shithub: qk2

Download patch

ref: 1f5f4dd3f933e0b5f39aeb9052740337d9ea0c0b
parent: 906d81a38cbbf650c29d0fe4b00912cb628a961f
author: qwx <>
date: Fri Jun 1 10:02:13 EDT 2018

import rogue, xatrix, crbot

diff: cannot open b/crbot//null: file does not exist: 'b/crbot//null' diff: cannot open b/doc/crbot/bots//null: file does not exist: 'b/doc/crbot/bots//null' diff: cannot open b/doc/crbot//null: file does not exist: 'b/doc/crbot//null' diff: cannot open b/doc//null: file does not exist: 'b/doc//null' diff: cannot open b/rogue//null: file does not exist: 'b/rogue//null' diff: cannot open b/xatrix//null: file does not exist: 'b/xatrix//null'
--- /dev/null
+++ b/LICENSE.crbot
@@ -1,0 +1,91 @@
+	LIMITED PROGRAM SOURCE CODE LICENSE
+
+     This Limited Program Source Code License (the "Agreement") is between Id Software, Inc., a Texas corporation, (hereinafter "Id Software") and Licensee (as defined below) and is made effective beginning on the date you, the Licensee, download the Code, as defined below, (the "Effective Date").  BY DOWNLOADING THE CODE, AS DEFINED BELOW, YOU, THE LICENSEE, AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.  YOU SHOULD READ THIS AGREEMENT CAREFULLY BEFORE DOWNLOADING THE CODE.  EVERY PERSON IN POSSESSION OF AN AUTHORIZED COPY, AS DEFINED BELOW, OF THE CODE SHALL BE SUBJECT TO THE TERMS AND CONDITIONS OF THIS AGREEMENT.
+
+	R E C I T A L S
+
+WHEREAS, Id Software is the owner and developer of the computer software program source code accompanied by this Agreement (the "Code");
+
+WHEREAS, Id Software desires to license certain limited non-exclusive rights regarding the Code to Licensee; and
+
+WHEREAS, Licensee desires to receive a limited license for such rights.
+
+	T E R M S    A N D    C O N D I T I O N S
+
+     NOW, THEREFORE, for and in consideration of the mutual premises contained herein and for other good and valuable consideration, the receipt and sufficiency of which is hereby acknowledged, the undersigned parties do hereby agree as follows:
+
+1.  Definitions.  The parties hereto agree the following definitions shall apply to this Agreement:
+
+     a.  "Authorized Copy" shall mean a copy of the Code obtained by Authorized Means, as defined below.  A copy of the Code is not an "Authorized Copy" unless it is accompanied by a copy of this Agreement and obtained by Authorized Means.  A Modified Copy, as defined below, is not an Authorized Copy;
+
+     b.  "Authorized Means" shall mean obtaining an Authorized Copy only by downloading the Authorized Copy from Id Software's Internet web site or from another web site authorized or approved by Id Software for such purposes or by obtaining an Authorized Copy by electronic means via the Internet;
+
+     c.  "Code" shall mean the computer software program source code which accompanies this Agreement and includes Code included within any Modified Copy and which is the code that constitutes the Authorized Copy;
+
+     d.  "Game" shall mean QUAKE II;
+
+     e.  "Licensee" shall mean you, the person who is in possession of an Authorized Copy by Authorized Means; and
+
+     f.  "Modified Copy" shall mean a copy of the Code first obtained by Authorized Means which is subsequently modified by Licensee, as provided in paragraph 2. below.
+
+2.  Grant of Rights.  Subject to the terms and provisions of this Agreement, Id Software hereby grants to Licensee and Licensee hereby accepts, a limited, world-wide (except as otherwise provided herein), non-exclusive, non-transferable, and non-assignable license to:  (i) use the Authorized Copy and the Modified Copy, as defined above, for the development by Licensee of extra levels operable with the Game (the "Extra Levels"); (ii) incorporate all or a portion of the Authorized Copy and the Modified Copy within the Extra Levels; (iii) distribute by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, the Authorized Copy and the Modified Copy to the extent such Modified Copy and such Authorized Copy, or a portion thereof, is included within the Extra Levels; (iv) distribute by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, by electronic transmission via  the Internet only the Authorized Copy without any alteration or modification along with a copy of this Agreement which must always accompany the Authorized Copy; (v) modify the Authorized Copy in order to create a Modified Copy, as defined above; and (vi) distribute the Modified Copy by way of a sublicense limited by the terms of this Agreement, free of charge and at no cost, by electronic transmission via the Internet only.  Each person or entity who/which receives a copy of the Code shall be subject to the terms of this Agreement but, no rights are granted to any person or entity who/which obtains, receives, or is in possession of any copy of the Code by other than Authorized Means.
+
+3.  Reservation of Rights and Prohibitions.  Id Software expressly reserves all rights not granted herein.  Licensee shall not make any use of the trademarks relating to the Game or Id Software (the "Trademarks").  Any use by Licensee of the Authorized Copy or the Modified Copy not expressly permitted in paragraph 2. above is expressly prohibited and any such unauthorized use shall constitute a material breach of this Agreement by Licensee.  Any use of the Code, whether included within a Modified Copy or otherwise, and/or the Authorized Copy not permitted in this Agreement shall constitute an infringement or violation of Id Software's copyright in the Code.  Licensee shall not copy, reproduce, manufacture or distribute (free of charge or otherwise) the Authorized Copy or the Modified Copy in any tangible media, including, without limitation, a CD ROM.  Licensee shall not commercially exploit by sale, lease, rental or otherwise the Authorized Copy or the Modified Copy whether included within Extra Levels or otherwise.  Licensee shall not commercially exploit by sale, lease, rental or otherwise any Extra Levels developed by the use of the Code, whether in whole or in part.  Licensee is not receiving any rights hereunder regarding the Game, the Trademarks or any audio-visual elements, artwork, sound, music, images, characters, or other element of the Game.  Licensee may modify the Authorized Copy in order to create a Modified Copy, as noted above, but all sublicensees who receive the Modified Copy shall not receive any rights to commercially exploit or to make any other use of the Code included therein except the right to use such Code for such sublicensee's personal entertainment. By way of example and not exclusion, a sublicensee for the Modified Copy shall not further modify the Code within the Modified Copy.  Only the Licensee who obtains the Code by Authorized Means shall be permitted to modify such Code on the terms as described in this Agreement.
+
+4.  Additional Obligations.  In addition to the obligations of Licensee otherwise set forth in this Agreement, during the Term, and thereafter where specified, Licensee agrees that: 
+
+     a.  Licensee will not attack or challenge the ownership by Id Software of the Code, the Authorized Copy, the Game, the Trademarks, or any copyright, patent or trademark or other intellectual property right related thereto and Licensee will not attack or challenge the validity of the license granted hereunder during the Term or thereafter; and
+
+     b.  Licensee will promptly inform Id Software of any unauthorized use of the Code, the Authorized Copy, the Trademarks, or the Game, or any portions thereof, and will reasonably assist Id Software in the enforcement of all rights Id Software may have against such unauthorized users.
+
+5.  Ownership.  Title to and all ownership rights in and to the Code, whether included within the Modified Copy, the Authorized Copy or otherwise, the Game, the Authorized Copy, and the Trademarks and the copyrights, trade secrets, trademarks, patents and all other intellectual property rights related thereto shall remain with Id Software which shall have the exclusive right to protect the same by copyright or otherwise.   Licensee shall have no ownership rights in or to the Game, the Code, the Authorized Copy or the Trademarks.  Licensee acknowledges that Licensee, by this Agreement, is only receiving a limited license to use the Authorized Copy, as specified in paragraph 2. of this Agreement.
+
+6.   Compliance with Applicable Laws.  In exercising Licensee's limited rights hereunder, Licensee shall comply with all applicable laws, [including, without limitation, 22 U.S.C., section 2778 and 22 U.S.C. C.F.R. Parts 120-130 (1995)] regulations, ordinances and statutes, including, but not limited to, the import/export laws and regulations of the United States and its governmental and regulatory agencies (including, without limitation, the Bureau of Export Administration and the U.S. Department of Commerce) and all applicable international treaties and laws.
+
+7.  Term and Termination.  
+
+     a.  The term of this Agreement and the license granted herein begins on the Effective Date and shall expire, without notice, on a date one (1) calendar year from the Effective Date (the "Term").  
+
+     b.  Either party may terminate this Agreement, for any reason or no reason, on thirty (30) days written notice to the other party.  Termination will be effective on the thirtieth (30th) day following delivery of the notice of termination.  Notwithstanding anything to the contrary herein, this Agreement shall immediately terminate, without the requirement of any notice from Id Software to Licensee, upon the occurrence of any of the following "Terminating Events":  (i) if Licensee  files a petition in bankruptcy; (ii) if Licensee makes an assignment for the benefit of creditors; (iii) if any bankruptcy proceeding or assignment for benefit of creditors is commenced against Licensee and not dismissed within sixty (60) days after the date of its commencement; (iv) the insolvency of Licensee; or (v) a breach, whether material or otherwise, of this Agreement by Licensee.  Upon the occurrence of a Terminating Event, this Agreement and any and all rights hereunder shall terminate without prejudice to any rights or claims Id Software may have, and all rights granted hereunder shall revert, without notice, to and be vested in Id Software.
+
+     c.  Termination or expiration of this Agreement shall not create any liability against Id Software and shall not relieve Licensee from any liability which arises prior to termination or expiration.  Upon expiration or earlier termination of this Agreement, Licensee shall have no further right to exercise the rights licensed hereunder or otherwise acquired in relation to this Agreement.
+
+8.  Licensee's Warranties.  Licensee warrants and represents that:   (i) Licensee has full legal rights and authority to enter into and become bound by the terms of this Agreement; (ii) Licensee has full legal rights and authority to perform Licensee?s obligations hereunder; (iii) Licensee will comply, at all times during the Term, with all applicable laws, as set forth hereinabove; (iv) all modifications which Licensee performs on the Code in order to create the Modified Copy and all non-Id Software property included within Extra Levels shall not infringe against or misappropriate any third party rights, including, without limitation, copyrights and trade secrets; and (v) the use or non-use of all modifications which Licensee performs on the Code in order to create the Modified Copy and all non-Id Software property included within Extra Levels shall not infringe against or misappropriate any third party rights, including, without limitation, copyrights and trade secrets.
+
+9.  Indemnification.  Licensee hereby agrees to indemnify, hold harmless and defend Id Software and Id Software's predecessors, successors, assigns, officers, directors, shareholders, employees, agents, representatives, licensees (but not including Licensee), sublicensees, distributors, attorneys and accountants (collectively, the "Id Related Parties") from and against any and all "Claims", which shall mean all damages, claims, losses, causes of action, liabilities, lawsuits, judgments and expenses (including, without limitation, reasonable attorneys' fees and expenses) arising from, relating to or in connection with (i) a breach of this Agreement by Licensee and/or (ii) Licensee's use or non-use of the Code, whether the Authorized Copy or whether a portion of the Code as may be included within the Modified Copy or within Extra Levels.  Id Software agrees to notify Licensee of any such Claims within a reasonable time after Id Software learns of same.  Licensee, at its own expense, shall defend Id Software and the Id Related Parties from and against any and all Claims.  Id Software and the Id Related Parties reserve the right to participate in any defense of the Claims with counsel of their choice, and at their own expense.  In the event Licensee fails to provide a defense, then Licensee shall be responsible for paying the attorneys' fees and expenses incurred by Id Software and the Id Related Parties regarding the defense of the Claims.  Id Software and the Id Related Parties, as applicable, agree to reasonably assist in the defense of the Claims.  No settlement by Licensee of any Claims shall be valid unless Licensee receives the prior written consent of Id Software and the Id Related Parties, as applicable, to any such settlement, with consent may be withheld in Id Software's and the Id Related Parties' sole discretion. 
+
+10.  Limitation of Liability.  UNDER NO CIRCUMSTANCES SHALL ID SOFTWARE BE LIABLE TO LICENSEE FOR ACTUAL, SPECIAL, INCIDENTAL, CONSEQUENTIAL OR PUNITIVE DAMAGES OR ANY OTHER DAMAGES, WHETHER OR NOT ID SOFTWARE RECEIVES NOTICE OF ANY SUCH DAMAGES.
+
+11.  Disclaimer of Warranties.  ID SOFTWARE EXPRESSLY DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, WITH REGARD TO THE CODE, THE AUTHORIZED COPY AND OTHERWISE.
+
+12.  Goodwill.  Licensee recognizes the great value of the goodwill associated with the Game and the Trademarks, and acknowledges that such goodwill, now existing and hereafter created, exclusively belongs to Id Software and that the Trademarks have acquired a secondary meaning in the mind of the public.
+
+13.  Remedies.  In the event of a breach of this Agreement by Id Software, Licensee's sole remedy shall be to terminate this Agreement by delivering written notice of termination to Id Software.  In the event of a breach by Licensee of this Agreement, Id Software may pursue the remedies to which Id Software is entitled under applicable law and this Agreement.  Licensee agrees that Licensee's unauthorized use of the Authorized Copy would immediately and irreparably damage Id Software, and in the event of such threatened or actual unauthorized use, Id Software shall be entitled to an injunctive order appropriately restraining and/or prohibiting such unauthorized use without the necessity of Id Software posting bond or other security.  Pursuit of any remedy by Id Software shall not constitute a waiver of any other right or remedy of Id Software under this Agreement or under applicable law.
+
+14.  Choice of Law, Venue and Service of Process.  This Agreement shall be construed in accordance with the laws of the State of Texas and applicable United States federal law and all claims and/or lawsuits in connection with this Agreement must be brought in Dallas County, Texas where exclusive venue shall lie.  Licensee hereby agrees that service of process by certified mail to the address set forth below, with return receipt requested, shall constitute valid service of process upon Licensee.  If for any reason Licensee has moved or cannot be validly served, then Licensee appoints the Secretary of State of the state of Texas to accept service of process on Licensee's behalf.
+
+15.  Delivery of Notices.  Unless otherwise directed in writing by the parties, all notices given hereunder shall be sent to the last known address of addressee.  All notices, requests, consents and other communications under this Agreement shall be in writing and shall be deemed to have been delivered on the date personally delivered or on the date deposited in the United States Postal Service, postage prepaid, by certified mail, return receipt requested, or telegraphed and confirmed, or delivered by electronic facsimile and confirmed.  Any notice to Id Software shall also be sent to its counsel: D. Wade Cloud, Jr., Hiersche, Martens, Hayward, Drakeley & Urbach, P.C., 15303 Dallas Parkway, Suite 700, LB 17, Dallas, Texas  75248.
+
+16.  No Partnership, Etc.  This Agreement does not constitute and shall not be construed as constituting a partnership or joint venture between Id Software and Licensee.  Neither party shall have any right to obligate or bind the other party in any manner whatsoever, and nothing herein contained shall give, or is intended to give, any rights of any kind to any third persons.
+
+17.  Entire agreement.  This Agreement constitutes the entire understanding between Licensee and Id Software regarding the subject matter hereof.  Each and every clause of this Agreement is severable from the whole and shall survive unless the entire Agreement is declared unenforceable.  No prior or present agreements or representations between the parties hereto regarding the subject matter hereof shall be binding upon the parties hereto unless incorporated in this Agreement.  No modification or change in this Agreement shall be valid or binding upon the parties hereto unless in writing and executed by the parties to be bound thereby.
+
+18.  Assignment.  This Agreement shall bind and inure to the benefit of Id Software, its successors and assigns, and Id Software may assign its rights hereunder, in Id Software's sole discretion.  This Agreement is personal to Licensee, and Licensee shall not assign, transfer, convey nor franchise its rights granted hereunder.  As provided above, Licensee may sublicense Licensee's limited rights herein by transferring the Authorized Copy by Authorized Means.  As noted, each sublicensee in possession of a copy of the Authorized Copy shall be subject to the terms and conditions of this Agreement.
+
+19.  Survival.  The following provisions shall survive the expiration or earlier termination of this Agreement:  paragraphs 5., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 19., 20.a. and 20.b.
+
+20.  Miscellaneous.  
+
+      a.  All captions in this Agreement are intended solely for the convenience of the parties, and none shall effect the meaning or construction of any provision.
+
+      b.  The terms and conditions of this Agreement have been negotiated fully and freely among the parties.  Accordingly, the preparation of this Agreement by counsel for a given party will not be material to the construction hereof, and the terms of this Agreement shall not be strictly construed against such party.
+
+BY DOWNLOADING THE CODE, AS DEFINED ABOVE, YOU, THE LICENSEE, AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.
+
+
+February 12, 1998
+
+
+LIMITED  PROGRAM SOURCE CODE LICENSE	PAGE 1
+
+
--- /dev/null
+++ b/LICENSE.xatrix.rogue
@@ -1,0 +1,327 @@
+        LIMITED PROGRAM SOURCE CODE LICENSE
+
+     This Limited Program Source Code License (the "Agreement") is between
+     Id Software, Inc., a Texas corporation, (hereinafter "Id Software")
+     and Licensee (as defined below) and is made effective beginning on
+     the date you, the Licensee, download the Code, as defined below,
+     (the "Effective Date").  BY DOWNLOADING THE CODE, AS DEFINED
+     BELOW, YOU, THE LICENSEE, AGREE TO ALL THE TERMS AND CONDITIONS OF
+     THIS AGREEMENT.  YOU SHOULD READ THIS AGREEMENT CAREFULLY BEFORE
+     DOWNLOADING THE CODE.  EVERY PERSON IN POSSESSION OF AN AUTHORIZED
+     COPY, AS DEFINED BELOW, OF THE CODE SHALL BE SUBJECT TO THE TERMS
+     AND CONDITIONS OF THIS AGREEMENT.
+
+        R E C I T A L S
+
+WHEREAS, Id Software is the owner and developer of the computer software
+program source code accompanied by this Agreement (the "Code");
+
+WHEREAS, Id Software desires to license certain limited non-exclusive
+rights regarding the Code to Licensee; and
+
+WHEREAS, Licensee desires to receive a limited license for such rights.
+
+        T E R M S    A N D    C O N D I T I O N S
+
+     NOW, THEREFORE, for and in consideration of the mutual premises
+     contained herein and for other good and valuable consideration,
+     the receipt and sufficiency of which is hereby acknowledged, the
+     undersigned parties do hereby agree as follows:
+
+1.  Definitions.  The parties hereto agree the following definitions
+shall apply to this Agreement:
+
+     a.  "Authorized Copy" shall mean a copy of the Code obtained by
+     Authorized Means, as defined below.  A copy of the Code is not
+     an "Authorized Copy" unless it is accompanied by a copy of this
+     Agreement and obtained by Authorized Means.  A Modified Copy,
+     as defined below, is not an Authorized Copy;
+
+     b.  "Authorized Means" shall mean obtaining an Authorized Copy only
+     by downloading the Authorized Copy from Id Software's Internet web
+     site or from another web site authorized or approved by Id Software
+     for such purposes or by obtaining an Authorized Copy by electronic
+     means via the Internet;
+
+     c.  "Code" shall mean the computer software program source code
+     which accompanies this Agreement and includes Code included within
+     any Modified Copy and which is the code that constitutes the
+     Authorized Copy;
+
+     d.  "Game" shall mean QUAKE II;
+
+     e.  "Licensee" shall mean you, the person who is in possession of
+     an Authorized Copy by Authorized Means; and
+
+     f.  "Modified Copy" shall mean a copy of the Code first obtained
+     by Authorized Means which is subsequently modified by Licensee,
+     as provided in paragraph 2. below.
+
+2.  Grant of Rights.  Subject to the terms and provisions of this
+Agreement, Id Software hereby grants to Licensee and Licensee hereby
+accepts, a limited, world-wide (except as otherwise provided herein),
+non-exclusive, non-transferable, and non-assignable license to:  (i)
+use the Authorized Copy and the Modified Copy, as defined above, for
+the development by Licensee of extra levels operable with the Game (the
+"Extra Levels"); (ii) incorporate all or a portion of the Authorized Copy
+and the Modified Copy within the Extra Levels; (iii) distribute by way
+of a sublicense limited by the terms of this Agreement, free of charge
+and at no cost, the Authorized Copy and the Modified Copy to the extent
+such Modified Copy and such Authorized Copy, or a portion thereof, is
+included within the Extra Levels; (iv) distribute by way of a sublicense
+limited by the terms of this Agreement, free of charge and at no cost, by
+electronic transmission via  the Internet only the Authorized Copy without
+any alteration or modification along with a copy of this Agreement which
+must always accompany the Authorized Copy; (v) modify the Authorized Copy
+in order to create a Modified Copy, as defined above; and (vi) distribute
+the Modified Copy by way of a sublicense limited by the terms of this
+Agreement, free of charge and at no cost, by electronic transmission via
+the Internet only.  Each person or entity who/which receives a copy of
+the Code shall be subject to the terms of this Agreement but, no rights
+are granted to any person or entity who/which obtains, receives, or is
+in possession of any copy of the Code by other than Authorized Means.
+
+3.  Reservation of Rights and Prohibitions.  Id Software expressly
+reserves all rights not granted herein.  Licensee shall not make any use
+of the trademarks relating to the Game or Id Software (the "Trademarks").
+Any use by Licensee of the Authorized Copy or the Modified Copy not
+expressly permitted in paragraph 2. above is expressly prohibited and
+any such unauthorized use shall constitute a material breach of this
+Agreement by Licensee.  Any use of the Code, whether included within
+a Modified Copy or otherwise, and/or the Authorized Copy not permitted
+in this Agreement shall constitute an infringement or violation of Id
+Software's copyright in the Code.  Licensee shall not copy, reproduce,
+manufacture or distribute (free of charge or otherwise) the Authorized
+Copy or the Modified Copy in any tangible media, including, without
+limitation, a CD ROM.  Licensee shall not commercially exploit by sale,
+lease, rental or otherwise the Authorized Copy or the Modified Copy
+whether included within Extra Levels or otherwise.  Licensee shall not
+commercially exploit by sale, lease, rental or otherwise any Extra Levels
+developed by the use of the Code, whether in whole or in part.  Licensee
+is not receiving any rights hereunder regarding the Game, the Trademarks
+or any audio-visual elements, artwork, sound, music, images, characters,
+or other element of the Game.  Licensee may modify the Authorized Copy in
+order to create a Modified Copy, as noted above, but all sublicensees who
+receive the Modified Copy shall not receive any rights to commercially
+exploit or to make any other use of the Code included therein except the
+right to use such Code for such sublicensee's personal entertainment. By
+way of example and not exclusion, a sublicensee for the Modified Copy
+shall not further modify the Code within the Modified Copy.  Only the
+Licensee who obtains the Code by Authorized Means shall be permitted to
+modify such Code on the terms as described in this Agreement.
+
+4.  Additional Obligations.  In addition to the obligations of Licensee
+otherwise set forth in this Agreement, during the Term, and thereafter
+where specified, Licensee agrees that:
+
+     a.  Licensee will not attack or challenge the ownership by Id
+     Software of the Code, the Authorized Copy, the Game, the Trademarks,
+     or any copyright, patent or trademark or other intellectual property
+     right related thereto and Licensee will not attack or challenge
+     the validity of the license granted hereunder during the Term or
+     thereafter; and
+
+     b.  Licensee will promptly inform Id Software of any unauthorized
+     use of the Code, the Authorized Copy, the Trademarks, or the Game,
+     or any portions thereof, and will reasonably assist Id Software
+     in the enforcement of all rights Id Software may have against such
+     unauthorized users.
+
+5.  Ownership.  Title to and all ownership rights in and to the Code,
+whether included within the Modified Copy, the Authorized Copy or
+otherwise, the Game, the Authorized Copy, and the Trademarks and the
+copyrights, trade secrets, trademarks, patents and all other intellectual
+property rights related thereto shall remain with Id Software which shall
+have the exclusive right to protect the same by copyright or otherwise.
+Licensee shall have no ownership rights in or to the Game, the Code,
+the Authorized Copy or the Trademarks.  Licensee acknowledges that
+Licensee, by this Agreement, is only receiving a limited license to use
+the Authorized Copy, as specified in paragraph 2. of this Agreement.
+
+6.   Compliance with Applicable Laws.  In exercising Licensee's
+limited rights hereunder, Licensee shall comply with all applicable
+laws, [including, without limitation, 22 U.S.C., section 2778 and 22
+U.S.C. C.F.R. Parts 120-130 (1995)] regulations, ordinances and statutes,
+including, but not limited to, the import/export laws and regulations
+of the United States and its governmental and regulatory agencies
+(including, without limitation, the Bureau of Export Administration
+and the U.S. Department of Commerce) and all applicable international
+treaties and laws.
+
+7.  Term and Termination.
+
+     a.  The term of this Agreement and the license granted herein
+     begins on the Effective Date and shall expire, without notice,
+     on a date one (1) calendar year from the Effective Date (the "Term").
+
+     b.  Either party may terminate this Agreement, for any reason or
+     no reason, on thirty (30) days written notice to the other party.
+     Termination will be effective on the thirtieth (30th) day following
+     delivery of the notice of termination.  Notwithstanding anything
+     to the contrary herein, this Agreement shall immediately terminate,
+     without the requirement of any notice from Id Software to Licensee,
+     upon the occurrence of any of the following "Terminating Events":
+     (i) if Licensee  files a petition in bankruptcy; (ii) if Licensee
+     makes an assignment for the benefit of creditors; (iii) if any
+     bankruptcy proceeding or assignment for benefit of creditors is
+     commenced against Licensee and not dismissed within sixty (60)
+     days after the date of its commencement; (iv) the insolvency of
+     Licensee; or (v) a breach, whether material or otherwise, of this
+     Agreement by Licensee.  Upon the occurrence of a Terminating Event,
+     this Agreement and any and all rights hereunder shall terminate
+     without prejudice to any rights or claims Id Software may have,
+     and all rights granted hereunder shall revert, without notice,
+     to and be vested in Id Software.
+
+     c.  Termination or expiration of this Agreement shall not create
+     any liability against Id Software and shall not relieve Licensee
+     from any liability which arises prior to termination or expiration.
+     Upon expiration or earlier termination of this Agreement, Licensee
+     shall have no further right to exercise the rights licensed hereunder
+     or otherwise acquired in relation to this Agreement.
+
+8.  Licensee's Warranties.  Licensee warrants and represents that:
+(i) Licensee has full legal rights and authority to enter into and
+become bound by the terms of this Agreement; (ii) Licensee has full
+legal rights and authority to perform Licensee?s obligations hereunder;
+(iii) Licensee will comply, at all times during the Term, with all
+applicable laws, as set forth hereinabove; (iv) all modifications which
+Licensee performs on the Code in order to create the Modified Copy and
+all non-Id Software property included within Extra Levels shall not
+infringe against or misappropriate any third party rights, including,
+without limitation, copyrights and trade secrets; and (v) the use or
+non-use of all modifications which Licensee performs on the Code in order
+to create the Modified Copy and all non-Id Software property included
+within Extra Levels shall not infringe against or misappropriate any third
+party rights, including, without limitation, copyrights and trade secrets.
+
+9.  Indemnification.  Licensee hereby agrees to indemnify, hold
+harmless and defend Id Software and Id Software's predecessors,
+successors, assigns, officers, directors, shareholders, employees,
+agents, representatives, licensees (but not including Licensee),
+sublicensees, distributors, attorneys and accountants (collectively,
+the "Id Related Parties") from and against any and all "Claims", which
+shall mean all damages, claims, losses, causes of action, liabilities,
+lawsuits, judgments and expenses (including, without limitation,
+reasonable attorneys' fees and expenses) arising from, relating to or in
+connection with (i) a breach of this Agreement by Licensee and/or (ii)
+Licensee's use or non-use of the Code, whether the Authorized Copy or
+whether a portion of the Code as may be included within the Modified
+Copy or within Extra Levels.  Id Software agrees to notify Licensee
+of any such Claims within a reasonable time after Id Software learns
+of same.  Licensee, at its own expense, shall defend Id Software and the
+Id Related Parties from and against any and all Claims.  Id Software and
+the Id Related Parties reserve the right to participate in any defense
+of the Claims with counsel of their choice, and at their own expense.
+In the event Licensee fails to provide a defense, then Licensee shall be
+responsible for paying the attorneys' fees and expenses incurred by Id
+Software and the Id Related Parties regarding the defense of the Claims.
+Id Software and the Id Related Parties, as applicable, agree to reasonably
+assist in the defense of the Claims.  No settlement by Licensee of any
+Claims shall be valid unless Licensee receives the prior written consent
+of Id Software and the Id Related Parties, as applicable, to any such
+settlement, with consent may be withheld in Id Software's and the Id
+Related Parties' sole discretion.
+
+10.  Limitation of Liability.  UNDER NO CIRCUMSTANCES SHALL ID SOFTWARE
+BE LIABLE TO LICENSEE FOR ACTUAL, SPECIAL, INCIDENTAL, CONSEQUENTIAL
+OR PUNITIVE DAMAGES OR ANY OTHER DAMAGES, WHETHER OR NOT ID SOFTWARE
+RECEIVES NOTICE OF ANY SUCH DAMAGES.
+
+11.  Disclaimer of Warranties.  ID SOFTWARE EXPRESSLY DISCLAIMS ALL
+WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE, WITH REGARD TO THE CODE, THE AUTHORIZED COPY AND OTHERWISE.
+
+12.  Goodwill.  Licensee recognizes the great value of the goodwill
+associated with the Game and the Trademarks, and acknowledges that such
+goodwill, now existing and hereafter created, exclusively belongs to Id
+Software and that the Trademarks have acquired a secondary meaning in
+the mind of the public.
+
+13.  Remedies.  In the event of a breach of this Agreement by Id Software,
+Licensee's sole remedy shall be to terminate this Agreement by delivering
+written notice of termination to Id Software.  In the event of a breach
+by Licensee of this Agreement, Id Software may pursue the remedies to
+which Id Software is entitled under applicable law and this Agreement.
+Licensee agrees that Licensee's unauthorized use of the Authorized
+Copy would immediately and irreparably damage Id Software, and in the
+event of such threatened or actual unauthorized use, Id Software shall
+be entitled to an injunctive order appropriately restraining and/or
+prohibiting such unauthorized use without the necessity of Id Software
+posting bond or other security.  Pursuit of any remedy by Id Software
+shall not constitute a waiver of any other right or remedy of Id Software
+under this Agreement or under applicable law.
+
+14.  Choice of Law, Venue and Service of Process.  This Agreement shall
+be construed in accordance with the laws of the State of Texas and
+applicable United States federal law and all claims and/or lawsuits
+in connection with this Agreement must be brought in Dallas County,
+Texas where exclusive venue shall lie.  Licensee hereby agrees that
+service of process by certified mail to the address set forth below,
+with return receipt requested, shall constitute valid service of process
+upon Licensee.  If for any reason Licensee has moved or cannot be validly
+served, then Licensee appoints the Secretary of State of the state of
+Texas to accept service of process on Licensee's behalf.
+
+15.  Delivery of Notices.  Unless otherwise directed in writing by
+the parties, all notices given hereunder shall be sent to the last
+known address of addressee.  All notices, requests, consents and other
+communications under this Agreement shall be in writing and shall be
+deemed to have been delivered on the date personally delivered or on the
+date deposited in the United States Postal Service, postage prepaid, by
+certified mail, return receipt requested, or telegraphed and confirmed,
+or delivered by electronic facsimile and confirmed.  Any notice to Id
+Software shall also be sent to its counsel: D. Wade Cloud, Jr., Hiersche,
+Martens, Hayward, Drakeley & Urbach, P.C., 15303 Dallas Parkway, Suite
+700, LB 17, Dallas, Texas  75248.
+
+16.  No Partnership, Etc.  This Agreement does not constitute and shall
+not be construed as constituting a partnership or joint venture between
+Id Software and Licensee.  Neither party shall have any right to obligate
+or bind the other party in any manner whatsoever, and nothing herein
+contained shall give, or is intended to give, any rights of any kind to
+any third persons.
+
+17.  Entire agreement.  This Agreement constitutes the entire
+understanding between Licensee and Id Software regarding the subject
+matter hereof.  Each and every clause of this Agreement is severable from
+the whole and shall survive unless the entire Agreement is declared
+unenforceable.  No prior or present agreements or representations
+between the parties hereto regarding the subject matter hereof shall be
+binding upon the parties hereto unless incorporated in this Agreement.
+No modification or change in this Agreement shall be valid or binding
+upon the parties hereto unless in writing and executed by the parties
+to be bound thereby.
+
+18.  Assignment.  This Agreement shall bind and inure to the benefit of
+Id Software, its successors and assigns, and Id Software may assign its
+rights hereunder, in Id Software's sole discretion.  This Agreement
+is personal to Licensee, and Licensee shall not assign, transfer,
+convey nor franchise its rights granted hereunder.  As provided above,
+Licensee may sublicense Licensee's limited rights herein by transferring
+the Authorized Copy by Authorized Means.  As noted, each sublicensee
+in possession of a copy of the Authorized Copy shall be subject to the
+terms and conditions of this Agreement.
+
+19.  Survival.  The following provisions shall survive the expiration
+or earlier termination of this Agreement:  paragraphs 5., 8., 9., 10.,
+11., 12., 13., 14., 15., 16., 17., 19., 20.a. and 20.b.
+
+20.  Miscellaneous.
+
+      a.  All captions in this Agreement are intended solely for the
+      convenience of the parties, and none shall effect the meaning or
+      construction of any provision.
+
+      b.  The terms and conditions of this Agreement have been negotiated
+      fully and freely among the parties.  Accordingly, the preparation
+      of this Agreement by counsel for a given party will not be material
+      to the construction hereof, and the terms of this Agreement shall
+      not be strictly construed against such party.
+
+BY DOWNLOADING THE CODE, AS DEFINED ABOVE, YOU, THE LICENSEE, AGREE TO
+ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.
+
+
+February 12, 1998
--- /dev/null
+++ b/crbot/cr_main.c
@@ -1,0 +1,5434 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+enum{
+	_DEBUG = 0
+};
+
+#define sqr(x) ((x)*(x))
+
+qboolean bVectorZero( vec3_t a )
+{
+	return fabs(a[0])<0.05f && fabs(a[1])<0.05f && fabs(a[2])<0.05f;
+}
+
+void YawVector( float yaw, vec3_t dir )
+{
+	float angle;
+	angle = yaw*( M_PI*2 / 360 );
+	dir[0] = cos(angle);
+	dir[1] = sin(angle);
+	dir[2] = 0;
+}
+
+#define dist(x,y)	(sqrt(sqr(x[0]-y[0])+sqr(x[1]-y[1])+sqr(x[2]-y[2])))
+#define dist2(x,y)	(sqr(x[0]-y[0])+sqr(x[1]-y[1])+sqr(x[2]-y[2]))
+#define dist2d(x,y)	(sqrt(sqr(x[0]-y[0])+sqr(x[1]-y[1])))
+#define dist2d2(x,y)	(sqr(x[0]-y[0])+sqr(x[1]-y[1]))
+
+//== from p_client.c
+void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
+void InitClientPersistant (gclient_t *client);
+void TossClientWeapon (edict_t *self);
+void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker);
+void CopyToBodyQue (edict_t *ent);
+
+//== from g_target.c
+void target_laser_start (edict_t *self);
+
+//== from g_items.c
+#define HEALTH_IGNORE_MAX       1
+qboolean Pickup_Health (edict_t *ent, edict_t *other);
+qboolean Pickup_Ammo (edict_t *ent, edict_t *other);
+qboolean Pickup_Armor (edict_t *ent, edict_t *other);
+qboolean Pickup_Weapon (edict_t *ent, edict_t *other);
+qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other);
+qboolean Pickup_Powerup(edict_t *ent, edict_t *other);
+qboolean Pickup_PowerArmor(edict_t *ent, edict_t *other);
+qboolean Pickup_Bandolier(edict_t *ent, edict_t *other);
+qboolean Pickup_Pack(edict_t *ent, edict_t *other);
+void Use_Quad( edict_t *ent, gitem_t *item );
+void Use_Invulnerability( edict_t *ent, gitem_t *item );
+void Use_Breather( edict_t *ent, gitem_t *item );
+void Weapon_Blaster (edict_t *ent);
+
+//== from g_monster.c
+void M_WorldEffects (edict_t *self);
+
+//== touch funcs
+void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator);
+//void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator);
+void door_use (edict_t *self, edict_t *other, edict_t *activator);
+
+void rocket_touch  (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+void blaster_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+//== local functions
+void cr_pain ( edict_t *self, edict_t *other, float kick, int damage );
+void cr_die ( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void cr_respawn( edict_t *self );
+
+void cr_sight(edict_t *self, edict_t *other);
+void cr_think (edict_t *self);
+void cr_think_pickup ( edict_t *self );
+void cr_think_temp_target ( edict_t *self );
+void cr_think_attack (edict_t *self);
+void cr_think_chase (edict_t *self);
+void cr_think_chase_route (edict_t *self);
+void cr_think_taunt (edict_t *self);
+void cr_think_flip (edict_t *self);
+void cr_think_salute (edict_t *self);
+void cr_think_run_for_life (edict_t *self);
+void cr_think_follow_route( edict_t *self );
+void cr_think_team_help( edict_t *self );
+void cr_think_team_group( edict_t *self );
+void cr_think_team_guard( edict_t *self );
+void cr_think_team_patrol( edict_t *self );
+void cr_think_wait( edict_t *self );
+
+void cr_run_frames( edict_t *self, int start, int end );
+void cr_animate_frames( edict_t *self, int start, int end );
+
+qboolean cr_fire_weapon( edict_t *self, edict_t *target );
+qboolean cr_moveto( edict_t *self );
+
+qboolean cr_find_route( edict_t *self, vec3_t target, qboolean b_important );
+
+void cr_init_node_net(void);
+void cr_init_node_keeper(void);
+
+
+//== sounds
+static int sound_footstep[4];
+
+//== constants
+const float BOT_MIN_SPEED = 120.f;
+const float BOT_MAX_SPEED = 300.f;
+
+const float BOT_MIN_ROT_SPEED = 10.f;
+const float BOT_MAX_ROT_SPEED = 90.f;
+
+const int   STEPSIZE         = 22;
+
+const int   TIME_WEAPON_CHANGE = 1.f;
+
+const int   CROUCH_DELTA = 32;
+
+const float WATER_SPEED_COEF  = 0.8f;
+const float CROUCH_SPEED_COEF = 0.6f;
+
+const float FIGHT_MSG_DELAY = 10;
+const float JUMP_DELAY = 0.2f;
+
+const float MESSAGE_DELAY = 10.f;
+
+const float ROCKET_MIN_AVOID_DIST =  30.f;
+const float ROCKET_AVOID_DIST     = 180.f;
+const float ROCKET_MAX_AVOID_DIST = 250.f;
+
+//=== weapons
+#define SPEED_MODIFIER 1.2f
+
+const int BLASTER_SPEED       = 1000;
+const int BLASTER_DAMAGE      = 15;
+const float TIME_BLASTER_SHOT = 0.5f*SPEED_MODIFIER;
+const float TIME_BLASTER_AFTERSHOT = 0.2f;
+
+const int SHOTGUN_SPEED       = -1;
+const int SHOTGUN_DAMAGE      = 4;
+const float TIME_SHOTGUN_SHOT = 1.2f*SPEED_MODIFIER;
+const float TIME_SHOTGUN_AFTERSHOT = 0.2f;
+
+const int SUPERSHOTGUN_SPEED       = -1;
+const int SUPERSHOTGUN_DAMAGE      = 6;
+const float TIME_SUPERSHOTGUN_SHOT = 1.2f*SPEED_MODIFIER;
+const float TIME_SUPERSHOTGUN_AFTERSHOT = 0.2f;
+
+const int MACHINEGUN_SPEED  = -1;
+const int MACHINEGUN_DAMAGE = 8;
+const float TIME_MACHINEGUN_SHOT = 0.1f;//*SPEED_MODIFIER;
+const float TIME_MACHINEGUN_AFTERSHOT = 0.2f;
+
+const int CHAINGUN_SPEED  = -1;
+const int CHAINGUN_DAMAGE = 6;
+const float TIME_CHAINGUN_SHOT = 0.04f;//*SPEED_MODIFIER;
+const float TIME_CHAINGUN_AFTERSHOT = 1.f;
+
+const int ROCKET_SPEED  = 650;
+const int ROCKET_DAMAGE = 100;
+const float TIME_ROCKET_SHOT = 0.85f*SPEED_MODIFIER;
+const float TIME_ROCKET_AFTERSHOT = 0.f;
+
+const int GRENADE_SPEED  = 600;
+const int GRENADE_DAMAGE = 120;
+const float TIME_GRENADE_SHOT = 1.2f*SPEED_MODIFIER;
+const float TIME_GRENADE_AFTERSHOT = 0.f;
+
+const int HYPERBLASTER_SPEED  = 1000;
+const int HYPERBLASTER_DAMAGE = 15;
+const float TIME_HYPERBLASTER_SHOT = 0.1f;//*SPEED_MODIFIER;
+const float TIME_HYPERBLASTER_AFTERSHOT = 0.5f;
+
+const int BFG_SPEED  = 400;
+const int BFG_DAMAGE = 200;
+const float TIME_BFG_SHOT = 1.2f;//*SPEED_MODIFIER;
+const float TIME_BFG_AFTERSHOT = 1.1f;
+const float TIME_BFG_SPINUP = 1.f;
+
+const int RAILGUN_SPEED  = -1;
+const int RAILGUN_DAMAGE = 100;
+const float TIME_RAILGUN_SHOT = 1.5f*SPEED_MODIFIER;
+const float TIME_RAILGUN_AFTERSHOT = 0.2f;
+
+//== team constants
+const float TEAM_HELP_TIME = 20;
+
+//== AI constants
+const float CHASE_TIME = 20;  // 20 secs.
+const float ATTACK_RANGE_MIN = 200;
+const float ATTACK_RANGE_MAX = 350;
+const float ENGAGE_RANGE_MIN = 300;
+const float ENGAGE_RANGE_MAX = 500;
+
+const float PICKUP_RANGE_MIN = 120;
+const float PICKUP_RANGE_MAX = 300;
+
+const float MELEE_COMBAT_DIST = 40;
+const float TOUCH_DIST = 10;
+
+const float UNREACHABLE_TIMEOUT       = 30;
+const float UNREACHABLE_ENEMY_TIMEOUT = 10;
+
+const float NODE_MAX_DIST = 280;
+const float NODE_MIN_DIST = 90;
+
+const float MOVE_JUMP_DIST = 30;
+const float JUMP_DIST      = 150;
+const float JUMP_HEIGHT    = 50;
+const float BOT_JUMP_SPEED = 300.f;
+
+const float ROCKETJUMP_MAXDIST = 320;
+const float ROCKETJUMP_DIST    = 200;
+const float ROCKETJUMP_MINDIST = 100;
+const float ROCKETJUMP_HEIGHT  = 220;
+
+const float TIME_STUCK       = 0.6f;
+const float TIME_NEXT_ENEMY_MIN = 0.3f;
+const float TIME_NEXT_ENEMY_MAX = 1.5f;
+const float TIME_NEXT_PICKUP = 0.8f;
+const float TIME_LONE_NODE   = 30.f;
+const float TIME_ROAM_DIR_CHANGE = 3.f;
+
+const float BOT_ACCURACY_VERT  = 0.3f;
+const float BOT_ACCURACY_HORZ  = 0.6f;
+
+const float TIME_CROUCH      = 0.5f;
+const float TIME_CHECK_STUCK = 0.6f;
+
+//== global net of nodes
+static path_node_t* cr_node_head   = NULL;
+static edict_t*     cr_node_keeper = NULL;
+static int          node_count     = 0;
+
+const int MAX_NODE_COUNT = 2000;
+
+//== number of bots respawned
+static int global_bot_number=1;
+
+static bot_info_pers_t* global_bots;
+static int global_bots_count;
+
+//== map cycle related
+#define MAX_MAP 256
+typedef char pathname[32];
+static int map_cycle_count=0, next_map=0;
+static pathname map_list[MAX_MAP];
+void cr_load_maplist(void);
+
+//== debug vars
+static edict_t* global_path_route=NULL;
+
+#define is_closer(pos1,pos2,xx) ((sqr((pos1)[0]-(pos2)[0])+sqr((pos1)[1]-(pos2)[1])+sqr((pos1)[2]-(pos2)[2]))<((xx)*(xx)))
+#define is_closer_b(pos1,pos2,xx) ( fabs((pos1)[0]-(pos2)[0])<(xx) && fabs((pos1)[1]-(pos2)[1])<(xx) && fabs((pos1)[2]-(pos2)[2])<(xx) )
+
+#define MAX_STACK_NODE 1024
+
+#define MAX_MSG_LEN  256
+#define MAX_MSG      4096
+
+static char* msg_pain[MAX_MSG];
+static int msg_pain_count=0, msg_pain_last=0;
+static char* msg_kill[MAX_MSG];
+static int msg_kill_count=0, msg_kill_last=0;
+static char* msg_death[MAX_MSG];
+static int msg_death_count=0, msg_death_last=0;
+static char* msg_fight[MAX_MSG];
+static int msg_fight_count=0, msg_fight_last=0;
+
+
+static int INDEX_SLUGS;
+static int INDEX_RAILGUN;
+static int INDEX_CELLS;
+static int INDEX_HYPERBLASTER;
+static int INDEX_ROCKETS;
+static int INDEX_ROCKET_LAUNCHER;
+static int INDEX_GRENADES;
+static int INDEX_GRENADE_LAUNCHER;
+static int INDEX_BULLETS;
+static int INDEX_CHAINGUN;
+static int INDEX_MACHINEGUN;
+static int INDEX_SHELLS;
+static int INDEX_SUPER_SHOTGUN;
+static int INDEX_SHOTGUN;
+static int INDEX_BLASTER;
+static int INDEX_POWER_SCREEN;
+static int INDEX_POWER_SHIELD;
+static int INDEX_QUAD;
+static int INDEX_INVULN;
+static int INDEX_BREATHER;
+static int INDEX_BFG;
+static int INDEX_TECH1, INDEX_TECH2, INDEX_TECH3, INDEX_TECH4;
+static int INDEX_FLAG1, INDEX_FLAG2;
+
+void cr_init_variables(void)
+{
+	INDEX_SLUGS = ITEM_INDEX(FindItem("slugs"));
+	INDEX_RAILGUN = ITEM_INDEX(FindItem("railgun"));
+	INDEX_CELLS = ITEM_INDEX(FindItem("cells"));
+	INDEX_BFG = ITEM_INDEX(FindItem("BFG10K"));
+	INDEX_HYPERBLASTER = ITEM_INDEX(FindItem("hyperblaster"));
+	INDEX_ROCKETS = ITEM_INDEX(FindItem("rockets"));
+	INDEX_ROCKET_LAUNCHER = ITEM_INDEX(FindItem("rocket launcher"));
+	INDEX_GRENADES = ITEM_INDEX(FindItem("grenades"));
+	INDEX_GRENADE_LAUNCHER = ITEM_INDEX(FindItem("grenade launcher"));
+	INDEX_BULLETS = ITEM_INDEX(FindItem("bullets"));
+	INDEX_CHAINGUN = ITEM_INDEX(FindItem("chaingun"));
+	INDEX_MACHINEGUN = ITEM_INDEX(FindItem("machinegun"));
+	INDEX_SHELLS = ITEM_INDEX(FindItem("shells"));
+	INDEX_SUPER_SHOTGUN = ITEM_INDEX(FindItem("super shotgun"));
+	INDEX_SHOTGUN = ITEM_INDEX(FindItem("shotgun"));
+	INDEX_BLASTER = ITEM_INDEX(FindItem("blaster"));
+	INDEX_POWER_SCREEN = ITEM_INDEX(FindItem("Power Screen"));
+	INDEX_POWER_SHIELD = ITEM_INDEX(FindItem("Power Shield"));
+	INDEX_INVULN = ITEM_INDEX(FindItem("Invulnerability"));
+	INDEX_QUAD = ITEM_INDEX(FindItem("Quad Damage"));
+	INDEX_BREATHER = ITEM_INDEX(FindItem("Rebreather"));
+	INDEX_TECH1 = ITEM_INDEX(FindItemByClassname("item_tech1"));
+	INDEX_TECH2 = ITEM_INDEX(FindItemByClassname("item_tech2"));
+	INDEX_TECH3 = ITEM_INDEX(FindItemByClassname("item_tech3"));
+	INDEX_TECH4 = ITEM_INDEX(FindItemByClassname("item_tech4"));
+	INDEX_FLAG1 = ITEM_INDEX(FindItemByClassname("item_flag_team1"));
+	INDEX_FLAG2 = ITEM_INDEX(FindItemByClassname("item_flag_team2"));
+}
+
+qboolean pos_visible ( vec3_t spot1, vec3_t spot2 )
+{
+	trace_t trace;
+	trace = gi.trace ( spot1, vec3_origin, vec3_origin, spot2, NULL, MASK_OPAQUE );
+	return (trace.fraction == 1.f);
+}
+
+qboolean pos_reachable ( vec3_t spot1, vec3_t spot2 )
+{
+	trace_t trace;
+	trace = gi.trace ( spot1, vec3_origin, vec3_origin, spot2, NULL, 
+		(CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_PLAYERCLIP) );
+	return (trace.fraction==1.f);
+}
+
+qboolean can_reach ( edict_t *self, edict_t *other )
+{
+	vec3_t  spot1;
+	vec3_t  spot2;
+
+	VectorCopy (self->s.origin, spot1);
+	spot1[2] += self->viewheight;
+	VectorCopy (other->s.origin, spot2);
+	spot2[2] += other->viewheight;
+
+	return pos_reachable (spot1,spot2);
+}
+
+qboolean cr_infront ( edict_t *self, edict_t *other )
+{
+	vec3_t  vec;
+	float   dot;
+	vec3_t  forward;
+
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	dot = DotProduct (vec, forward);
+
+	return dot > (13-self->bot_pers->skill)*0.06f;
+}
+
+qboolean is_touched( edict_t* self, vec3_t pos )
+{
+	if ( pos[0] < (self->s.origin[0] + self->mins[0]) ) return false;
+	if ( pos[0] > (self->s.origin[0] + self->maxs[0]) ) return false;
+	if ( pos[1] < (self->s.origin[1] + self->mins[1]) ) return false;
+	if ( pos[1] > (self->s.origin[1] + self->maxs[1]) ) return false;
+	if ( pos[2] < (self->s.origin[2] + self->mins[2]) ) return false;
+	if ( pos[2] > (self->s.origin[2] + self->maxs[2]) ) return false;
+	return true;
+}
+
+qboolean is_touched2d( edict_t* self, vec3_t pos )
+{
+	if ( pos[0] < (self->s.origin[0] + self->mins[0]) ) return false;
+	if ( pos[0] > (self->s.origin[0] + self->maxs[0]) ) return false;
+	if ( pos[1] < (self->s.origin[1] + self->mins[1]) ) return false;
+	if ( pos[1] > (self->s.origin[1] + self->maxs[1]) ) return false;
+	return true;
+}
+
+void line_think ( edict_t* self )
+{
+	VectorCopy ( self->pos2, self->s.old_origin);
+
+	self->nextthink = level.time + FRAMETIME;
+	self->think = G_FreeEdict;
+}
+
+#define COLOR_BEAM_GREEN  4
+#define COLOR_BEAM_BLUE   8
+
+void cr_draw_line ( vec3_t pos1, vec3_t pos2, int col, edict_t** chain )
+{
+	edict_t *beam;
+	beam = G_Spawn();
+
+	beam->spawnflags = 1 | col;
+	beam->dmg = 0.001;
+	beam->classname = "laser line";
+
+	target_laser_start (beam);
+
+	VectorCopy( pos1, beam->s.origin );
+	VectorCopy( pos2, beam->pos2 );
+
+	beam->think = line_think;
+	beam->nextthink = level.time + FRAMETIME;
+
+	gi.linkentity (beam);
+
+	if (chain) {
+		beam->flags |= FL_TEAMSLAVE;
+		beam->teamchain = *chain;
+		*chain = beam;
+	}
+}
+
+void cr_marker( vec3_t pos )
+{
+	edict_t* t;
+	t = G_Spawn();
+	VectorSet( t->mins, -10, -10, -10);
+	VectorSet( t->maxs, 10, 10, 10);
+	VectorClear( t->velocity );
+	VectorCopy( pos, t->s.origin );
+	t->classname = "debug marker";
+	t->model = "models/items/adrenal/tris.md2";
+	t->s.modelindex = gi.modelindex(t->model);
+
+	t->nextthink = level.time + 30;
+	t->think = G_FreeEdict;
+
+	gi.linkentity(t);
+
+	//  gi.bprintf ( PRINT_MEDIUM, "pos %.1f %.1f %.1f\n", pos[0], pos[1], pos[2] );
+}
+
+void cr_set_move_target( edict_t* self, vec3_t target )
+{
+	VectorCopy( target, self->bot_info->move_target );
+	self->bot_info->move_target[2] += STEPSIZE;
+}
+
+void cr_add_direct_route( path_node_t* node1, path_node_t* node2, qboolean bComputeDistance )
+{
+	qboolean bAdd;
+	int      i, j;
+	float    d;
+	// establish links
+
+	if (!node1 || !node2 || node1==node2) return;
+
+	//if(_DEBUG)
+	//	cr_draw_line( node1->position, node2->position, COLOR_BEAM_BLUE, NULL );
+
+	bAdd=false;
+	for ( i=0; i<MAX_NODE_LINKS; i++ ) {
+		if ( node1->link_to[i]==node2 ) break;
+		if (!node1->link_to[i]) {
+			bAdd=true;
+			break;
+		}
+	}
+	if (!bAdd) return;
+
+	bAdd=false;
+	for ( j=0; j<MAX_NODE_LINKS; j++ ) {
+		if ( node2->link_from[j]==node1 ||
+			!node2->link_from[j] ) {
+			bAdd=true;
+			break;
+		}
+	}
+	if (!bAdd) return;
+
+	node1->link_to[i] = node2;
+	node2->link_from[j] = node1;
+
+	if (bComputeDistance) d=dist( node1->position, node2->position );
+	else d=1.f;
+	node1->link_dist[i] = d;
+}
+
+void cr_add_direct_route_uni( path_node_t* node1, path_node_t* node2 )
+{
+	if (!node1 || !node2 || node1==node2) return;
+	cr_add_direct_route( node1, node2, true );
+	cr_add_direct_route( node2, node1, true );
+}
+
+void cr_remove_direct_route( path_node_t* node1, path_node_t* node2 )
+{
+	int i, r;
+
+	if (!node1 || !node2 || node1==node2) return;
+
+	r=-1;
+	for ( i=0; i<MAX_NODE_LINKS; i++ ) {
+		if (!node1->link_to[i]) break;
+		if ( node1->link_to[i]==node2 ) r=i;
+	}
+	i--;
+
+	if (i>=0 && r>=0) {
+		node1->link_to[r] = node1->link_to[i];
+		node1->link_to[i] = NULL;
+	}
+
+	r=-1;
+	for ( i=0; i<MAX_NODE_LINKS; i++ ) {
+		if (!node2->link_from[i]) break;
+		if ( node2->link_from[i]==node1 ) r=i;
+	}
+	i--;
+
+	if (i>=0 && r>=0) {
+		node2->link_from[r] = node2->link_from[i];
+		node2->link_from[i] = NULL;
+	}
+}
+
+void cr_message_pain ( edict_t* self, edict_t* other )
+{
+	int n;
+	if (!bot_chat->value) return;
+	if (!other || !other->inuse || !other->client || other->bot_info) return;
+	if (msg_pain_count<2) return;
+	if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
+	self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
+	while (1) {
+		n = rand() % msg_pain_count;
+		if (n==msg_pain_last) continue;
+		msg_pain_last=n;
+		gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_pain[n] );
+		break;
+	}
+}
+
+void cr_message_kill ( edict_t* self, edict_t* other )
+{
+	int n;
+	if (!bot_chat->value) return;
+	if (!other || !other->inuse || !other->client || other->bot_info) return;
+	if (msg_kill_count<2) return;
+	if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
+	self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
+	while (1) {
+		n = rand() % msg_kill_count;
+		if (n==msg_kill_last) continue;
+		msg_kill_last=n;
+		gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_kill[n] );
+		break;
+	}
+}
+
+void cr_message_death ( edict_t* self, edict_t* other )
+{
+	int n;
+	if (!bot_chat->value) return;
+	if (!other || !other->inuse || !other->client || other->bot_info) return;
+	if (msg_death_count<2) return;
+	if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
+	self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
+	while (1) {
+		n = rand() % msg_death_count;
+		if (n==msg_death_last) continue;
+		msg_death_last=n;
+		gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_death[n] );
+		break;
+	}
+}
+
+void cr_message_fight ( edict_t* self, edict_t* other )
+{
+	int n;
+	if (!bot_chat->value) return;
+	if (!other || !other->inuse || !other->client || other->bot_info) return;
+	if (msg_fight_count<2) return;
+	if ( (level.time-self->bot_info->time_last_message)<MESSAGE_DELAY ) return;
+	self->bot_info->time_last_message = level.time - qrandom()*MESSAGE_DELAY/4;
+	while (1) {
+		n = rand() % msg_fight_count;
+		if (n==msg_fight_last) continue;
+		msg_fight_last=n;
+		gi.cprintf ( other, PRINT_CHAT, "%s: %s\n", self->client->pers.netname, msg_fight[n] );
+		break;
+	}
+}
+
+qboolean cr_get_avoid_vector( edict_t* self, vec3_t av )
+{
+	edict_t *hit;
+	float    d, old_d;
+	vec3_t   pos, rpos, avoid_vect, dir_v;
+	int      i;
+	qboolean bRes;
+
+	VectorCopy( self->s.origin, pos );
+	VectorClear( av );
+
+	bRes = false;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+		if ( hit->touch != rocket_touch  &&
+			hit->touch != Grenade_Touch && 
+			(self->bot_pers->skill<6 || hit->touch!=blaster_touch || ((rand()&3)==3)) ) continue;
+		if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+
+		VectorCopy( hit->s.origin, rpos );
+		old_d = sqr(pos[0]-rpos[0])+sqr(pos[1]-rpos[1])+sqr(pos[2]-rpos[2]);
+		if (old_d>(ROCKET_MAX_AVOID_DIST*ROCKET_MAX_AVOID_DIST)) continue;
+		VectorMA( rpos, FRAMETIME/2, hit->velocity, rpos );
+		for (i=0; i<7; i++) {
+			avoid_vect[0] = pos[0]-rpos[0];
+			avoid_vect[1] = pos[1]-rpos[1];
+			avoid_vect[2] = pos[2]-rpos[2];
+			d = sqr(avoid_vect[0])+sqr(avoid_vect[1])+sqr(avoid_vect[2]);
+			if (d>(ROCKET_MAX_AVOID_DIST*ROCKET_MAX_AVOID_DIST)) break;
+			if (d>old_d) break;
+			// old_d = d;
+			d = sqrt(d);
+			if (d<ROCKET_MIN_AVOID_DIST) {
+				// move bot perpendicular to rocket speed
+				CrossProduct( avoid_vect, hit->velocity, dir_v );
+				if (self->groundentity) {
+					dir_v[0]=0;
+					dir_v[1]=0;
+					dir_v[2] = dir_v[2]>0 ? 1.f : -1.f;
+				}
+				CrossProduct( hit->velocity, dir_v, avoid_vect );
+				VectorNormalize( avoid_vect );
+				VectorMA( av, 2*ROCKET_AVOID_DIST/ROCKET_MIN_AVOID_DIST, avoid_vect, av );
+			}
+			else {
+				VectorMA( av, ROCKET_AVOID_DIST/(d*d), avoid_vect, av );
+			}
+			bRes = true;
+			VectorMA( rpos, FRAMETIME, hit->velocity, rpos );
+		}
+	}
+
+	return bRes;
+}
+
+void cr_avoid_rocket( edict_t* self )
+{
+	vec3_t av;
+	float  yaw;
+
+	if ( self->bot_info->time_next_rocket_avoid>level.time ) return;
+	self->bot_info->time_next_rocket_avoid = level.time + FRAMETIME*(10-self->bot_pers->skill);
+
+	if (!cr_get_avoid_vector(self,av)) return;
+
+	// gi.dprintf ( "avoid: %.1f %.1f %.1f\n", av[0], av[1], av[2] );
+
+	yaw = self->ideal_yaw*M_PI*2/360;
+	av[0] += cos(yaw);
+	av[1] += sin(yaw);
+	self->ideal_yaw = vectoyaw(av);
+}
+
+void cr_add_links_radius( path_node_t *new_node )
+{
+	path_node_t *node;
+	float        max_dist;
+
+	max_dist = NODE_MIN_DIST*1.4f;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		if ( is_closer_b( new_node->position, node->position, max_dist) && 
+			pos_reachable( new_node->position, node->position ) )
+			cr_add_direct_route_uni( node, new_node );
+		if (new_node->link_to[MAX_NODE_LINKS-1]) return;
+	}
+
+	max_dist = NODE_MAX_DIST;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		if ( is_closer_b( new_node->position, node->position, max_dist) && 
+			pos_reachable( new_node->position, node->position ) )
+			cr_add_direct_route_uni( node, new_node );
+		if (new_node->link_to[MAX_NODE_LINKS-1]) return;
+	}
+}
+
+path_node_t* cr_find_touched_node ( edict_t *self, vec3_t pos )
+{
+	path_node_t *best, *node;
+
+	VectorCopy ( self->s.origin, self->s.old_origin);
+	VectorCopy ( pos, self->s.origin );
+
+	best = NULL;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		if ( is_touched(self,node->position) ) {
+			best = node;
+			break;
+		}
+	}
+
+	VectorCopy ( self->s.old_origin, self->s.origin );
+	return best;
+}
+
+path_node_t *
+cr_insert_node(vec3_t pos, path_node_t* d, int flags)
+{
+	path_node_t *p;
+
+	if(_DEBUG)
+		cr_marker(pos);
+
+	p = Z_TagMalloc(sizeof *p, TAG_GAME);
+	memset(p, 0, sizeof *p);
+	p->time = level.time;
+
+	VectorCopy(pos, p->position);
+
+	if(d != nil)
+		cr_add_direct_route_uni(p, d);
+
+	// try to establish links to all visible nodes around
+	cr_add_links_radius(p);
+
+	// link new node in
+	p->next = cr_node_head;
+	cr_node_head = p;
+
+	p->flags = flags;
+
+	return p;
+}
+
+float distance( edict_t *e1, edict_t *e2 )
+{
+	vec3_t  vec;
+
+	if (!e1 || !e2) return 1e32;
+
+	VectorSubtract ( e1->s.origin, e2->s.origin, vec);
+	return VectorLength (vec);
+}
+
+void cr_add_unreachable( edict_t *self, edict_t *other, float timeout )
+{
+	int         i, best_i;
+	float       best_time;
+	bot_info_t* bi = self->bot_info;
+
+	if (!other) return;
+
+	best_time = 1e32;
+	best_i=0;
+	for ( i=0; i<MAX_UNREACHABLES; i++ ) {
+		if ( bi->unreachable[i]==other || !bi->unreachable[i] ) {
+			best_i = i;
+			break;
+		}
+		if (bi->time_unreachable[i]<best_time) {
+			best_i = i;
+			best_time = bi->time_unreachable[i];
+		}
+	}
+
+	bi->unreachable[best_i] = other;
+	bi->time_unreachable[best_i] = level.time + timeout*(0.9f+qrandom()*0.2f);
+
+	// gi.bprintf ( PRINT_MEDIUM, "marking %s as unreachable for %.1f sec. (%d)\n", other->classname, bi->time_unreachable[best_i]-level.time, best_i );
+}
+
+qboolean cr_check_unreachable( edict_t *self, edict_t *other )
+{
+	int i;
+	bot_info_t* bi = self->bot_info;
+
+	if (!other) return true;
+
+	for ( i=0; i<MAX_UNREACHABLES; i++ ) {
+		if (!bi->unreachable[i]) continue;
+		if ( bi->time_unreachable[i]<level.time ) {
+			// remove it
+			// gi.bprintf ( PRINT_MEDIUM, "(1) removing %s from unreachables\n", bi->unreachable[i]->classname );
+			bi->unreachable[i] = NULL;
+			bi->time_unreachable[i] = 0;
+		}
+		else 
+			if (bi->unreachable[i]==other) return true;
+	}
+
+	return false;
+}
+
+void cr_remove_unreachable( edict_t *self, edict_t *other )
+{
+	int i;
+	bot_info_t* bi = self->bot_info;
+
+	if (!other) return;
+
+	for ( i=0; i<MAX_UNREACHABLES; i++ ) {
+		if (!bi->unreachable[i]) continue;
+		if ( bi->time_unreachable[i]<level.time ||
+			bi->unreachable[i]==other ) {
+			// remove it
+			bi->unreachable[i] = NULL;
+			bi->time_unreachable[i] = 0;
+			// gi.bprintf ( PRINT_MEDIUM, "(2) removing %s from unreachables\n", other->classname );
+		}
+	}
+}
+
+void cr_forget_pickup_target( edict_t *self )
+{
+	self->bot_info->pickup_target = NULL;
+	self->bot_info->pickup_target_score = 1e32;
+}
+
+void cr_forget_enemy( edict_t *self )
+{
+	self->oldenemy = self->enemy;
+	self->enemy = NULL;
+}
+
+qboolean cr_low_health( edict_t *self )
+{
+	return self->health<(self->max_health/8);
+}
+
+qboolean cr_avoid_fight( edict_t *self )
+{
+	return (self->client->pers.inventory[INDEX_FLAG1]>0) ||
+		(self->client->pers.inventory[INDEX_FLAG2]>0);
+}
+
+qboolean cr_find_enemy( edict_t *self )
+{
+	edict_t *client, *best_client;
+	float    d, best_dist;
+	int      min_light_level;
+
+	if (cr_avoid_fight(self)) return false;
+	if (cr_low_health(self)) return false;
+
+	min_light_level = (14 - self->bot_pers->skill)*.4f;
+
+	if (self->enemy && self->bot_info->time_next_enemy>level.time) {
+		self->oldenemy = self->enemy;
+		VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
+		self->monsterinfo.trail_time = level.time;
+		return true;
+	}
+	self->bot_info->time_next_enemy = level.time + 
+		TIME_NEXT_ENEMY_MIN + (TIME_NEXT_ENEMY_MAX-TIME_NEXT_ENEMY_MIN)*0.1f*self->bot_pers->skill;
+
+	if (self->client->pers.weapon == FindItem ("blaster")) {
+		if (qrandom()<0.8f) return false;
+	}
+
+	best_client=NULL;
+	best_dist=1e32;
+
+	for ( client=g_edicts+1; client<=(g_edicts+game.maxclients); client++) {
+		if (!client->inuse || client==self || (client->flags & FL_NOTARGET)) continue;
+
+		// attack only bots from other teams, unless we are in team #0
+		if ( self->client->pers.team_no>0 && 
+			client->client->pers.team_no==self->client->pers.team_no ) continue;
+
+		// is it dead already?
+		if ( client->deadflag>DEAD_DYING || (client->svflags & SVF_NOCLIENT) ) continue;
+		if (cr_check_unreachable(self,client)) continue;
+
+		// can we see him?
+		if (client!=self->oldenemy && !cr_infront (self,client)) continue;
+		if (!can_reach(self,client)) continue;
+
+		d = distance(self,client);
+
+		// is client in an spot too dark to be seen?
+		if (!client->bot_info && client->light_level<=min_light_level) {
+			if ( client->light_level < (d-100)*0.002f*min_light_level ) continue;
+		}
+
+		if ( !best_client || d<best_dist ) {
+			best_client = client;
+			best_dist = d;
+			if (best_dist<MELEE_COMBAT_DIST) break;
+		}
+	}
+
+	if (!best_client) return false;
+
+	if ( !self->enemy &&
+		self->bot_info->time_next_shot < level.time ) {
+		self->bot_info->time_next_shot = level.time + 0.05f*(11-self->bot_pers->skill)*(1.f+0.2f*qrandom());
+	}
+
+	cr_forget_enemy(self);
+	self->enemy = best_client;
+	VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
+	self->monsterinfo.trail_time = level.time;
+
+	cr_forget_pickup_target(self);
+
+	return true;
+}
+
+void
+cr_create_bot_structure(edict_t *p)
+{
+	p->bot_pers = Z_TagMalloc(sizeof *p->bot_pers, TAG_GAME);
+	memset(p->bot_pers, 0, sizeof *p->bot_pers);
+	p->bot_info = Z_TagMalloc(sizeof *p->bot_info, TAG_GAME);
+	memset(p->bot_info, 0, sizeof *p->bot_info);
+}
+
+void cr_compute_skills( edict_t *self, int skill_level )
+{
+	self->bot_pers->speed = BOT_MIN_SPEED + (BOT_MAX_SPEED-BOT_MIN_SPEED)*0.1f*skill_level;
+	self->bot_pers->rot_speed = BOT_MIN_ROT_SPEED + (BOT_MAX_ROT_SPEED-BOT_MIN_ROT_SPEED)*0.1f*skill_level;
+	self->bot_pers->attack_range = ATTACK_RANGE_MIN + (ATTACK_RANGE_MAX-ATTACK_RANGE_MIN)*0.1f*skill_level;
+	self->bot_pers->engage_range = ENGAGE_RANGE_MIN + (ENGAGE_RANGE_MAX-ENGAGE_RANGE_MIN)*0.1f*skill_level;
+
+	self->bot_pers->adapt_count = 0;
+}
+
+void cr_register_new_bot( bot_info_pers_t* new_bot )
+{
+	int i;
+
+	for ( i=0; i<global_bots_count; i++ ) {
+		if (global_bots[i].b_inuse) continue;
+		memcpy( global_bots+i, new_bot, sizeof(bot_info_pers_t) );
+		global_bots[i].b_inuse = true;
+		break;
+	}
+}
+
+void cr_unregister_bot( bot_info_pers_t* bot )
+{
+	int i;
+
+	for ( i=0; i<global_bots_count; i++ ) {
+		if (!global_bots[i].b_inuse ||
+			bot->playernum!=global_bots[i].playernum) continue;
+		memset(global_bots+i,0,sizeof(bot_info_pers_t));
+		game.clients[bot->playernum].inuse = false;
+		global_bots[i].b_inuse = false;
+	}
+}
+
+int cr_find_unused_client_slot (void)
+{
+	edict_t  *cl_ent;
+	int       i;
+
+	for ( i=0; i<game.maxclients; i++ ) {
+		cl_ent = g_edicts + 1 + i;
+		if ( !cl_ent->inuse && !cl_ent->client->inuse ) return i;
+	}
+	return -1;
+}
+
+void cr_init_per_info( edict_t* self )
+{
+	strcpy ( self->client->pers.netname, self->bot_pers->name );
+	self->client->pers.team_no = self->bot_pers->team_no;
+
+	sprintf ( self->client->pers.userinfo, "\\msg\\1\\rate\\25000\\name\\%s\\skin\\%s\\fov\\90\\hand\\0\\ip\\loopback", self->bot_pers->name, self->bot_pers->skin );
+}
+
+void cr_update_userinfo( bot_info_pers_t* bot_pers )
+{
+	gi.configstring( CS_PLAYERSKINS+bot_pers->playernum, 
+		va( "%s\\%s", bot_pers->name, bot_pers->skin ) );
+}
+
+
+void SP_crbot( char* name, int skill_level, char* skin, int team, char* model )
+{
+	edict_t *new_crbot;
+	int      playernum;
+
+	if (!deathmatch->value) {
+		gi.dprintf ( "Start a deathmatch game first or add '+set deathmatch 1' to the command line\n" );
+		return;
+	}
+
+	playernum = cr_find_unused_client_slot();
+	if (playernum<0) {
+		gi.dprintf ( "*** Not enough client slots to create a new bot.\n*** Increase 'maxclients' var and restart the map.\n*** You can do it by typing 'maxclients #' in the console window or\n*** by adding '+set maxclients #' to your command line.\n*** To restart the map type 'map map_name_here'\n" );
+		return;
+	}
+
+	// spawn the bot on a spawn spot
+	new_crbot = g_edicts + playernum + 1; //G_Spawn();
+	G_InitEdict(new_crbot);
+	cr_create_bot_structure(new_crbot);
+
+	//=== init bot_pers
+	new_crbot->bot_pers->b_adapting = (skill_level==0);
+	new_crbot->bot_pers->playernum = playernum;
+
+	if (skill_level<1)  skill_level=5;
+	if (skill_level>10) skill_level=10;
+	new_crbot->bot_pers->skill = skill_level;
+	cr_compute_skills( new_crbot, skill_level );
+
+	new_crbot->bot_pers->team_no = team;
+	if (!skin || !*skin) skin = bot_skin->string;
+	strncpy( new_crbot->bot_pers->skin, skin, 64 );
+	if (!model || !*model) model = bot_model->string;
+	sprintf( new_crbot->bot_pers->model, "players/%s/tris.md2", model );
+	if (!strchr(new_crbot->bot_pers->skin,'/')) {
+		char nm[128];
+		sprintf( nm, "%s/%s", bot_model->string, new_crbot->bot_pers->skin );
+		strncpy( new_crbot->bot_pers->skin, nm, 64 );
+	}
+
+	if (!name || !*name) sprintf( new_crbot->bot_pers->name, "crbot%d", global_bot_number++ );
+	else strcpy ( new_crbot->bot_pers->name, name );
+	//===
+	cr_update_userinfo(new_crbot->bot_pers);
+	cr_register_new_bot(new_crbot->bot_pers);
+
+	// new_crbot->client = &game.clients[new_crbot->bot_pers->playernum]; // not required any more
+	new_crbot->client->inuse = true;
+	InitClientResp(new_crbot->client);
+	InitClientPersistant(new_crbot->client);
+
+	cr_init_per_info(new_crbot);
+
+	new_crbot->classname = "new_bot";
+	new_crbot->think = cr_respawn;
+	new_crbot->nextthink = level.time + qrandom();
+}
+
+qboolean cr_force_attack_enemy( edict_t*self, edict_t *enemy )
+{
+	if (cr_find_route( self, enemy->s.origin, true )) {
+		VectorCopy( enemy->s.origin, self->monsterinfo.last_sighting );
+		self->monsterinfo.trail_time = level.time;
+		self->enemy = enemy;
+		self->think = cr_think_chase_route;
+		return true;
+	}
+	return false;
+}
+
+void cr_call_teamers( edict_t*self, edict_t *enemy )
+{
+	edict_t* bot;
+
+	if (self->client->pers.team_no<=0) return;
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if ( bot->client->pers.team_no==self->client->pers.team_no && 
+			!cr_low_health(bot) &&
+			!bot->enemy && (bot->think==cr_think || bot->think==cr_think_pickup) ) {
+			cr_force_attack_enemy(bot,enemy);
+		}
+	}
+}
+
+void cr_team_help( edict_t* self )
+{
+	edict_t* bot;
+
+	if (self->client->pers.team_no<=0) {
+		gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
+		return;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if ( bot->client->pers.team_no!=self->client->pers.team_no || 
+			cr_low_health(self) ||
+			bot->enemy || (bot->think!=cr_think && bot->think!=cr_think_pickup) ) continue;
+
+		if (!cr_find_route( bot, self->s.origin, true )) {
+			gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
+		}
+		else {
+			gi.cprintf ( self, PRINT_CHAT, 
+				qrandom()<0.5f ? "%s: Yes, Sir!\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
+			bot->bot_info->team_leader = self;
+			bot->bot_info->bot_assignment = ASSN_HELP;
+			bot->think = cr_think_team_help;
+		}
+	}
+}
+
+void cr_team_group( edict_t* self )
+{
+	edict_t* bot;
+
+	if (self->client->pers.team_no<=0) {
+		gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
+		return;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if ( bot->client->pers.team_no!=self->client->pers.team_no || 
+			cr_low_health(self) ||
+			bot->enemy || (bot->think!=cr_think && bot->think!=cr_think_pickup) ) continue;
+
+		if (!cr_find_route( bot, self->s.origin, true )) {
+			gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
+		}
+		else {
+			gi.cprintf ( self, PRINT_CHAT, 
+				qrandom()<0.6f ? "%s: Yes, Sir!\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
+			bot->bot_info->team_leader = self;
+			bot->bot_info->bot_assignment = ASSN_GROUP;
+			bot->think = cr_think_team_group;
+		}
+	}
+}
+
+void cr_team_patrol( edict_t* self, char* name )
+{
+	edict_t* bot;
+	char* bot_name = NULL;
+
+	if (name && *name) bot_name=name;
+
+	if (self->client->pers.team_no<=0) {
+		gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
+		return;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if (bot->client->pers.team_no!=self->client->pers.team_no) continue;
+		if (cr_low_health(self) || bot->enemy) continue;
+		if ( bot->bot_info->bot_assignment==ASSN_NONE && 
+			bot->think!=cr_think && bot->think!=cr_think_pickup ) continue;
+
+		if (!bot_name) {
+			if (!infront(self,bot)) continue;
+			if (!pos_visible(self->s.origin,bot->s.origin)) continue;
+		}
+		else
+			if (cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
+
+		if (!cr_find_route( bot, self->s.origin, true )) {
+			gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
+		}
+		else {
+			gi.cprintf ( self, PRINT_CHAT, 
+				qrandom()<0.6f ? "%s: Yes, Sir! Patroling the area.\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
+			bot->bot_info->team_leader = self;
+			VectorCopy ( self->s.origin, bot->bot_info->bot_anchor );
+			bot->bot_info->bot_assignment = ASSN_PATROL;
+			bot->think = cr_think_team_patrol;
+		}
+	}
+}
+
+void cr_team_guard( edict_t* self, char* name )
+{
+	edict_t* bot;
+	char* bot_name = NULL;
+
+	if (name && *name) bot_name=name;
+
+	if (self->client->pers.team_no<=0) {
+		gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
+		return;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if (bot->client->pers.team_no!=self->client->pers.team_no) continue;
+		if (cr_low_health(self) || bot->enemy) continue;
+		if ( bot->bot_info->bot_assignment==ASSN_NONE && 
+			bot->think!=cr_think && bot->think!=cr_think_pickup ) continue;
+
+		if (!bot_name) {
+			if (!infront(self,bot)) continue;
+			if (!pos_visible(self->s.origin,bot->s.origin)) continue;
+		}
+		else
+			if (cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
+
+		if (!cr_find_route( bot, self->s.origin, true )) {
+			gi.cprintf ( self, PRINT_CHAT, "%s: Don't know how to get to your location, Sir!\n", bot->client->pers.netname );
+		}
+		else {
+			gi.cprintf ( self, PRINT_CHAT, 
+				qrandom()<0.6f ? "%s: Yes, Sir! Guarding the area.\n" : "%s: On my way, Sir!\n", bot->client->pers.netname );
+			bot->bot_info->team_leader = self;
+			VectorCopy ( self->s.origin, bot->bot_info->bot_anchor );
+			bot->bot_info->bot_assignment = ASSN_GUARD;
+			bot->think = cr_think_team_guard;
+		}
+	}
+}
+
+void cr_dismiss( edict_t* self )
+{
+	self->bot_info->team_leader = NULL;
+	self->bot_info->bot_assignment = ASSN_NONE;
+	VectorClear(self->bot_info->bot_anchor);
+}
+
+void cr_team_free( edict_t* self, char* name )
+{
+	char* bot_name = NULL;
+	edict_t* bot;
+
+	if (name && *name) bot_name=name;
+
+	if (self->client->pers.team_no<=0) {
+		gi.cprintf( self, PRINT_HIGH, "You are not in any team!\n");
+		return;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if ( !bot->inuse || bot==self || bot->deadflag==DEAD_DEAD ||
+			cistrcmp( bot->classname, "bot" )!=0 ) continue;
+
+		if ( bot->client->pers.team_no!=self->client->pers.team_no ) continue;
+		if ( bot_name && cistrcmp(bot->client->pers.netname,bot_name)!=0 ) continue;
+
+		if (bot->bot_info->bot_assignment!=ASSN_NONE) {
+			gi.cprintf( self, PRINT_CHAT, "%s: Yes, Sir!\n", bot->client->pers.netname );
+			cr_dismiss(bot);
+		}
+	}
+}
+
+void cr_pain ( edict_t *self, edict_t *other, float /*kick*/, int /*damage*/ )
+{
+	if (self->deadflag==DEAD_DEAD) return;
+
+	if (level.time>self->pain_debounce_time) {
+		int r, l;
+
+		if (qrandom()<0.4f) cr_message_pain ( self, other );
+
+		self->pain_debounce_time = level.time + 0.8f;
+
+		r = 1 + (rand()&1);
+		if (self->health < 25) l = 25;
+		else if (self->health < 50) l = 50;
+		else if (self->health < 75) l = 75;
+		else l = 100;
+		gi.sound ( self, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 0.6f, ATTN_NORM, 0);
+	}
+
+	if (other && other->client) {
+		if (other!=self && self->client->pers.team_no>0) {
+			// call other teamers!
+			if ( self->client->pers.team_no != other->client->pers.team_no && 
+				level.time > self->bot_info->time_next_callforhelp ) {
+				cr_call_teamers( self, other );
+				self->bot_info->time_next_callforhelp = level.time + 5.f;
+			}
+		}
+
+		if ( other!=self && other!=self->enemy && 
+			(!self->enemy || qrandom()<0.3f) && other->client &&
+			( self->client->pers.team_no<=0 || 
+			self->client->pers.team_no!=other->client->pers.team_no || qrandom()<0.2f ) ) {
+			cr_forget_pickup_target(self);
+			cr_forget_enemy(self);
+
+			self->enemy = other;
+			cr_remove_unreachable( self, self->enemy );
+
+			if ( level.time > self->bot_info->time_next_shot )
+				self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE*(11-self->bot_pers->skill)/5;
+
+			self->think = cr_think_chase;
+		}
+	}
+}
+
+edict_t* cr_respawn_bot( bot_info_pers_t* bot_pers, client_respawn_t* resp )
+{
+	edict_t  *new_crbot;
+
+	new_crbot = g_edicts + bot_pers->playernum + 1; //G_Spawn();
+	G_InitEdict(new_crbot);
+	cr_create_bot_structure(new_crbot);
+
+	*new_crbot->bot_pers = *bot_pers;
+
+	// new_crbot->client = &game.clients[new_crbot->bot_pers->playernum]; // not requaired any more
+	new_crbot->client->inuse = true;
+	InitClientResp(new_crbot->client);
+	InitClientPersistant(new_crbot->client);
+
+	if (resp) new_crbot->client->resp = *resp;
+
+	cr_init_per_info(new_crbot);
+
+	new_crbot->classname = "bot";
+	new_crbot->think = cr_respawn;
+	new_crbot->nextthink = level.time + 2.f+qrandom(); // 2-3 secs
+	return new_crbot;
+}
+
+void cr_free_bot( edict_t *self )
+{
+	gi.TagFree(self->bot_pers);
+	self->bot_pers=NULL;
+	gi.TagFree(self->bot_info);
+	self->bot_info=NULL;
+
+	// G_FreeEdict (self);
+	self->s.modelindex = 0;
+	self->s.modelindex2 = 0;
+	self->solid = SOLID_NOT;
+	self->inuse = false;
+	self->classname = "disconnected";
+}
+
+void cr_death( edict_t *self )
+{
+	if ( self->s.frame < (self->client->anim_end-10) ) {
+		self->s.frame = self->client->anim_end-6;
+	}
+
+	self->s.frame++;
+	if (self->s.frame<self->client->anim_end) {
+		self->nextthink = level.time + FRAMETIME;
+	}
+	else {
+		bot_info_pers_t  save_bot_pers;
+		client_respawn_t resp;
+
+		self->s.frame = self->client->anim_end;
+		save_bot_pers = *self->bot_pers;
+		resp = self->client->resp;
+
+		self->client->anim_priority = ANIM_BASIC;
+
+		if ( self->takedamage!=DAMAGE_NO &&
+			self->s.frame >= FRAME_crdeath1 ) {
+			self->s.effects = 0;
+			self->s.renderfx = 0;
+			CopyToBodyQue(self);
+		}
+
+		cr_free_bot( self );
+		cr_respawn_bot( &save_bot_pers, &resp );
+	}
+}
+
+void cr_die ( edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	self->think = cr_death;
+	self->nextthink = level.time + FRAMETIME;
+
+	VectorClear (self->avelocity);
+
+	self->takedamage = DAMAGE_YES;
+	self->movetype = MOVETYPE_TOSS;
+
+	self->s.modelindex2 = 0;    // remove linked weapon model
+	self->s.modelindex3 = 0;    // remove linked ctf flag
+	self->s.angles[0] = 0;
+	self->s.angles[2] = 0;
+
+	self->s.sound = 0;
+	self->client->weapon_sound = 0;
+
+	self->maxs[2] = -8;
+
+	self->svflags |= SVF_DEADMONSTER;
+
+	if (!self->deadflag) {
+		if (attacker && attacker->client && !attacker->bot_info) self->bot_pers->adapt_count++;
+		ClientObituary( self, inflictor, attacker );
+		CTFFragBonuses(self, inflictor, attacker);
+		TossClientWeapon(self);
+		CTFPlayerResetGrapple(self);
+		CTFDeadDropFlag(self);
+		CTFDeadDropTech(self);
+		self->client->pers.weapon = NULL;
+		memset ( self->client->pers.inventory, 0, sizeof(self->client->pers.inventory) );
+		if (qrandom()<0.4f) cr_message_death( self, attacker );
+	}
+
+	// remove powerups
+	self->client->quad_framenum = 0;
+	self->client->invincible_framenum = 0;
+	self->client->breather_framenum = 0;
+	self->client->enviro_framenum = 0;
+
+	if (self->health < -40) {
+		int n;
+
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for ( n=0; n<3; n++)
+			ThrowGib ( self, rand()&1 ? "models/objects/gibs/bone/tris.md2" :
+				"models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC );
+		self->takedamage = DAMAGE_NO;
+		self->client->anim_priority = ANIM_DEATH;
+		self->client->anim_end = 0;
+	}
+	else {
+		if (!self->deadflag) {
+			static int i;
+
+			i = (i+1)%3;
+			// start a death animation
+			self->client->anim_priority = ANIM_DEATH;
+			if (self->bot_info->b_crouch) {
+				self->s.frame = FRAME_crdeath1-1;
+				self->client->anim_end = FRAME_crdeath5;
+			}
+			else switch (i) {
+			case 0:
+				self->s.frame = FRAME_death101-1;
+				self->client->anim_end = FRAME_death106;
+				break;
+			case 1:
+				self->s.frame = FRAME_death201-1;
+				self->client->anim_end = FRAME_death206;
+				break;
+			case 2:
+				self->s.frame = FRAME_death301-1;
+				self->client->anim_end = FRAME_death308;
+				break;
+			}
+			gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
+		}
+	}
+
+	self->deadflag = DEAD_DEAD;
+
+	gi.linkentity(self);
+}
+
+
+path_node_t* cr_get_next_path_node( edict_t *self )
+{
+	self->bot_info->path_nodes--;
+	if (self->bot_info->path_nodes<0) {
+		self->bot_info->path_nodes=-1;
+		return NULL;
+	}
+	return self->bot_info->path[self->bot_info->path_nodes];
+}
+
+path_node_t* cr_get_current_path_node( edict_t *self )
+{
+	if (self->bot_info->path_nodes<0) return NULL;
+	return self->bot_info->path[self->bot_info->path_nodes];
+}
+
+path_node_t* cr_find_closest_node ( edict_t *self )
+{
+	float        d, min_dist, min_idist;
+	path_node_t *best, *node, *ibest;
+	vec3_t       pos;
+
+	VectorCopy ( self->s.origin, pos );
+
+	best = NULL;
+	ibest = NULL;
+	min_dist = 1e32;
+	min_idist = 1e32;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		d = dist2( pos, node->position );
+		if (d<min_idist) {
+			min_idist=d;
+			ibest=node;
+		}
+		if (d>min_dist) continue;
+		if (is_touched(self,node->position)) {
+			best = node;
+			break;
+		}
+		if (!pos_reachable(pos,node->position)) continue;
+		//if (!can_reach( self, node->position )) continue;
+		best = node;
+		min_dist = d;
+	}
+
+	// can't find any visible nodes :(
+	if (!best) best=ibest;
+
+	return best;
+}
+
+path_node_t* cr_find_closest_enemy_node( edict_t *self )
+{
+	path_node_t* node;
+
+	if (!self->enemy) return NULL;
+
+	VectorCopy ( self->s.origin, self->s.old_origin);
+	VectorCopy ( self->enemy->s.origin, self->s.origin );
+	node = cr_find_closest_node(self);
+	VectorCopy ( self->s.old_origin, self->s.origin );
+
+	return node;
+}
+
+path_node_t* cr_find_closest_node_pos ( edict_t* self, vec3_t pos )
+{
+	path_node_t* node;
+
+	VectorCopy ( self->s.origin, self->s.old_origin);
+	VectorCopy ( pos, self->s.origin );
+	node = cr_find_closest_node(self);
+	VectorCopy ( self->s.old_origin, self->s.origin );
+
+	return node;
+}
+
+void cr_register_position( edict_t* self, int flags )
+{
+	vec3_t       pos;
+	path_node_t *node;
+	int          n;
+	float        d;
+
+	VectorCopy ( self->s.origin, pos );
+
+	if (self->prev_node) {
+		if ( is_closer_b( pos, self->prev_node->position, NODE_MIN_DIST ) &&
+			pos_reachable( pos, self->prev_node->position ) ) return;
+	}
+
+	// must be far enough from all other nodes
+	n=0;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		d = dist2( pos, node->position );
+		if (d<50*50) break;
+		if (d>(NODE_MAX_DIST*NODE_MAX_DIST*0.5f)) continue;
+		if (!pos_reachable( pos, node->position )) continue;
+		n++;
+		if (d>NODE_MIN_DIST*NODE_MIN_DIST) continue;
+		if (n>=2) break;
+	}
+
+	if (!node)
+		self->prev_node = cr_insert_node( pos, self->prev_node, flags );
+	else 
+		self->prev_node = node;
+}
+
+void cr_update_routes ( edict_t *self )
+{
+	vec3_t       pos;
+
+	if (self->watertype & (CONTENTS_LAVA|CONTENTS_SLIME)) return;
+	if (!self->groundentity && self->waterlevel==0) return;
+
+	if (node_count>MAX_NODE_COUNT) return;
+
+	VectorCopy ( self->s.origin, pos );
+
+	if (self->bot_info->last_node) {
+		if ( is_closer_b( pos, self->bot_info->last_node->position, NODE_MIN_DIST ) &&
+			pos_reachable( pos, self->bot_info->last_node->position ) ) return;
+	}
+	if (self->bot_info->next_node) {
+		if ( is_closer_b( pos, self->bot_info->next_node->position, NODE_MIN_DIST ) &&
+			pos_reachable( pos, self->bot_info->next_node->position ) ) return;
+	}
+
+	cr_register_position( self, self->bot_info->b_on_ladder ? NF_LADDER : 0 );
+}
+
+void cr_pick_random_destination( edict_t * self )
+{
+	float d, an;
+
+	d = NODE_MIN_DIST + (NODE_MAX_DIST-NODE_MIN_DIST)*qrandom();
+	an = qrandom()*M_PI*2;
+	self->bot_info->move_target[0] = self->s.origin[0] + d*cos(an);
+	self->bot_info->move_target[1] = self->s.origin[1] + d*sin(an);
+	self->bot_info->move_target[2] = self->s.origin[2] + STEPSIZE;
+
+	// gi.bprintf ( PRINT_MEDIUM, "roaming to %.1f %.1f %.1f\n", self->bot_info->move_target[0], self->bot_info->move_target[1], self->bot_info->move_target[2] );
+}
+
+float cr_pickup_importance ( edict_t *self, edict_t *item_ent  )
+{
+	float    res = -1;
+	gitem_t* item = item_ent->item;
+
+	if (!item) return -1;
+
+	if (item->pickup==CTFPickup_Flag) {
+		if (item_ent->spawnflags & DROPPED_ITEM) {
+			res = 5000;
+		}
+		else {
+			if (strcmp(item_ent->classname, "item_flag_team1") == 0) {
+				if ( self->client->resp.ctf_team != CTF_TEAM1 ||
+					self->client->pers.inventory[INDEX_FLAG2] ) res = 5000;
+			}
+			else 
+				if (strcmp(item_ent->classname, "item_flag_team2") == 0) {
+					if ( self->client->resp.ctf_team != CTF_TEAM2 ||
+						self->client->pers.inventory[INDEX_FLAG1] ) res = 1100;
+				}
+		}
+	}
+	else
+		if (item->pickup==CTFPickup_Tech) {
+			//FIXME: teach bot to drop old powerup!
+			if ( !self->client->pers.inventory[INDEX_TECH1] && 
+				!self->client->pers.inventory[INDEX_TECH2] && 
+				!self->client->pers.inventory[INDEX_TECH3] && 
+				!self->client->pers.inventory[INDEX_TECH4] ) {
+				switch (item_ent->classname[9]) {
+				case '1':
+					res = 1000;
+					break;	// no breaks in original code because ????
+				case '2':
+					res = 1500;
+					break;
+				case '3':
+					res = 1500;
+					break;
+				case '4':
+					res = 2000;
+				}
+			}
+		}
+		else 
+			if (item->pickup==Pickup_Adrenaline) {
+				res = (95-self->health)*4;
+				if (res<0) res=-1;
+			}
+			else
+				if (item->pickup==Pickup_Powerup) {
+					if (item->use==Use_Quad ||
+						item->use==Use_Invulnerability) res=1000;
+					else if (item->use==Use_Breather) res=100;
+				}
+				else
+					if (item->pickup==Pickup_PowerArmor) {
+						res = (self->flags & FL_POWER_ARMOR) ? 1 : 1000;
+					}
+					else
+						if (item->pickup==Pickup_Health) {
+							//== health
+							if (item_ent->style & HEALTH_IGNORE_MAX) res=item_ent->count*10;
+							else {
+								if (self->health >= self->max_health)
+/* code folded from here */
+	/* code folded from here */
+	res=-1;
+	/* unfolding */
+/* unfolding */
+								else 
+/* code folded from here */
+	/* code folded from here */
+	res = cr_low_health(self) ? 20*item_ent->count : 0.2f*item_ent->count*(self->max_health - self->health);
+/* unfolding */
+								/* unfolding */
+							}
+						}
+						else
+							//== armor
+							if (item->pickup==Pickup_Armor) {
+								int old_armor_index;
+								gitem_armor_t *newinfo;
+
+								// get info on new armor
+								newinfo = (gitem_armor_t *)item->info;
+
+								old_armor_index = ArmorIndex(self);
+
+								// handle armor shards specially
+								if (item->tag == ARMOR_SHARD) {
+/* code folded from here */
+	/* code folded from here */
+	res = 5.f;
+	/* unfolding */
+/* unfolding */
+								}
+								else
+/* code folded from here */
+	/* code folded from here */
+	if (!old_armor_index) {
+		res = 2.f*newinfo->base_count;
+	}
+	else 
+		res = 2.f*(newinfo->base_count - self->client->pers.inventory[old_armor_index]);
+/* unfolding */
+								/* unfolding */
+								if (cr_low_health(self)) res*=4;
+							}
+							else
+								//== weapon
+								if (item->pickup==Pickup_Weapon) {
+/* code folded from here */
+	/* code folded from here */
+	int      index, ammo_idx;
+	qboolean bWeaponsStay = ((int)(dmflags->value) & DF_WEAPONS_STAY);
+
+	index = ITEM_INDEX(item);
+
+	if ( self->client->pers.inventory[index]>0 ) res = bWeaponsStay ? -1 : 0.5f;
+	else
+		if ( index>ITEM_INDEX(self->client->pers.weapon) )  {
+			ammo_idx = ITEM_INDEX(FindItem(item->ammo));
+			if ( self->client->pers.inventory[ammo_idx]>0 ||
+				!(item_ent->spawnflags & DROPPED_ITEM) )
+				res = self->client->pers.weapon == FindItem ("blaster") ? 400 : 100;
+			else 
+				res = bWeaponsStay ? 2.f : 5.f; // this weapon is better than we have, 
+			// but we don't have ammo for it yet
+		}
+		else
+			res = 0.5f; // this weapon is worse than we have
+	/* unfolding */
+/* unfolding */
+								}
+								else
+/* code folded from here */
+	/* code folded from here */
+	//== ammo
+	if (item->pickup==Pickup_Ammo) {
+		int  count, cur_count, max=0, mod=1, index;
+
+		index = ITEM_INDEX(item);
+		if (item_ent->count>0) count = item_ent->count;
+		else count = item->quantity;
+
+		if (item->tag == AMMO_BULLETS)       {
+			max = self->client->pers.max_bullets;
+			mod=1;
+			if (self->client->pers.inventory[INDEX_MACHINEGUN] || self->client->pers.inventory[INDEX_CHAINGUN]) mod*=2;
+		}
+		else if (item->tag == AMMO_SHELLS)   {
+			max = self->client->pers.max_shells;
+			mod=1;
+			if (self->client->pers.inventory[INDEX_SUPER_SHOTGUN] || self->client->pers.inventory[INDEX_SHOTGUN]) mod*=2;
+		}
+		else if (item->tag == AMMO_ROCKETS)  {
+			max = self->client->pers.max_rockets;
+			mod=3;
+			if (self->client->pers.inventory[INDEX_ROCKET_LAUNCHER]) mod*=2;
+		}
+		else if (item->tag == AMMO_GRENADES) {
+			max = self->client->pers.max_grenades;
+			mod=2;
+			if (self->client->pers.inventory[INDEX_GRENADE_LAUNCHER]) mod*=2;
+		}
+		else if (item->tag == AMMO_CELLS)    {
+			max = self->client->pers.max_cells;
+			mod=3;
+			if (self->client->pers.inventory[INDEX_HYPERBLASTER] || self->client->pers.inventory[INDEX_BFG]) mod*=3;
+		}
+		else if (item->tag == AMMO_SLUGS)    {
+			max = self->client->pers.max_slugs;
+			mod=2;
+			if (self->client->pers.inventory[INDEX_RAILGUN]) mod*=2;
+		}
+
+		cur_count = self->client->pers.inventory[index];
+		if (cur_count>=max) res = -1;
+		else res = mod*10.f*count*(max-cur_count)/sqr(max);
+	}
+	else 
+		if (item->pickup==Pickup_Bandolier) {
+			if (self->client->pers.max_bullets < 250) res = 50;
+			else res =  5;
+		}
+		else
+			if (item->pickup==Pickup_Pack) {
+				if (self->client->pers.max_bullets < 300) res = 100;
+				else res = 10;
+			}
+/* unfolding */
+	/* unfolding */
+	//FIXME: support the rest of pickups (items, etc.)
+
+	if (res<=0) res=-1;
+	return res;
+}
+
+float last_map_search=0;
+
+void cr_find_pickup_on_map( edict_t *self )
+{
+	path_node_t *node_stack1[MAX_STACK_NODE], *node_stack2[MAX_STACK_NODE];
+	path_node_t *node, *link_node, *next_link;
+	path_node_t **nodes, **new_nodes;
+	int          cur_stack, stack_nodes, new_stack_nodes, i, j, max_nodes, n_count;
+	float        cur_dist, best_dist, d, imp, best_score;
+	path_node_t *best_node;
+	edict_t*     hit;
+	// qboolean  b_show=false;
+
+	if (last_map_search==level.time) return;
+	last_map_search=level.time;
+
+	self->bot_info->last_node = cr_find_closest_node(self);
+
+	if (self->bot_info->bot_assignment==ASSN_GROUP) return;
+
+	// initialize node network
+	for ( node=cr_node_head; node!=NULL; node=node->next ) node->route_dist=-1;
+
+	self->bot_info->last_node->route_dist = 0.001;
+	node_stack1[0] = self->bot_info->last_node;
+	new_stack_nodes = 1;
+	cur_stack = 0;
+
+	max_nodes = 20 + 2*self->bot_pers->skill;
+	if (max_nodes>(MAX_PATH_NODES-2)) max_nodes = MAX_PATH_NODES-2;
+
+	best_score = self->bot_info->pickup_target_score;
+	best_node =  NULL;
+	n_count=0;
+	while ( (best_score>TOUCH_DIST || n_count<(max_nodes/2)) && 
+		n_count<max_nodes) {
+		stack_nodes = new_stack_nodes;
+		new_stack_nodes = 0;
+
+		if (cur_stack==0) {
+			nodes     = node_stack1;
+			new_nodes = node_stack2;
+			cur_stack=1;
+		}
+		else {
+			nodes     = node_stack2;
+			new_nodes = node_stack1;
+			cur_stack=0;
+		}
+
+		best_dist = 1e32;
+		for ( i=0; i<stack_nodes; i++ ) {
+			node = *nodes++;
+			cur_dist = node->route_dist;
+			if (best_dist>cur_dist) best_dist=cur_dist;
+
+			// go through all links
+			for ( j=0; j<MAX_NODE_LINKS; j++ ) {
+				link_node = node->link_to[j];
+				if ( !link_node ) break;
+				d = cur_dist + node->link_dist[j];
+
+				if (link_node->route_dist<0 && new_stack_nodes<MAX_STACK_NODE) {
+					// add new downlink node
+					*new_nodes = link_node;
+					new_nodes++;
+					new_stack_nodes++;
+				}
+
+				if ( link_node->route_dist<0 || link_node->route_dist>d ) {
+					// update route_dist if this route is shorter
+					link_node->route_dist = d;
+					hit = link_node->item;
+
+					if (!hit) continue;
+					if (!hit->inuse) continue;
+					if ( (hit->svflags & SVF_NOCLIENT) || hit->solid==SOLID_NOT ) continue;
+					if (cr_check_unreachable(self,hit)) continue;
+					imp = cr_pickup_importance(self,hit);
+					if (imp>0) {
+						d /= imp;
+						if (!best_node || d<best_score) {
+							best_score = d;
+							best_node = link_node;
+						}
+					}
+					/*
+               if (imp>1001) {
+                 b_show = true;
+                 gi.dprintf( "%s scored %.1f\n", hit->classname, d );
+                 } */
+				}
+			}
+		}
+
+		if (!new_stack_nodes) break;
+		n_count++;
+	}
+
+	/*
+   // remove path indication
+    { edict_t* line, *next_line;
+    for ( line = global_path_route; line!=NULL; line=next_line ) {
+       next_line = line->teamchain;
+       G_FreeEdict(line);
+       }
+    } */
+
+	if (!best_node) return;
+
+	self->bot_info->path_nodes=-1;
+	n_count=0;
+	node = best_node;
+	while (n_count<=max_nodes) {
+		// search for shortest possible distance from "node"
+		best_dist=0;
+		link_node=NULL;
+		for ( i=0; i<MAX_NODE_LINKS; i++ ) {
+			if ( !(next_link = node->link_from[i]) ) break;
+			d = next_link->route_dist;
+			if (d>=0 && (!link_node || best_dist>d) ) {
+				link_node = next_link;
+				best_dist = d;
+			}
+		}
+
+		self->bot_info->path[++self->bot_info->path_nodes]=node;
+
+		if (!link_node || link_node==self->bot_info->last_node) break;
+
+		node->route_dist = -2;
+
+		if(_DEBUG)
+			cr_draw_line( node->position, link_node->position, COLOR_BEAM_BLUE, &global_path_route );
+		node = link_node;
+		n_count++;
+	}
+
+	self->think = cr_think_pickup;
+	self->bot_info->pickup_target = best_node->item;
+	self->bot_info->pickup_target_score = best_score;
+	self->bot_info->next_node = node;
+	cr_set_move_target( self, node->position );
+
+	//   if (b_show) {
+	//    gi.bprintf ( PRINT_MEDIUM, "gonna get %s with score %.1f from map\n", self->bot_info->pickup_target->classname, best_score );
+	//     }
+}
+
+qboolean cr_force_pickup_target(  edict_t *self, edict_t *target )
+{
+	if (cr_find_route( self, target->s.origin, true )) {
+		self->think = cr_think_pickup;
+		self->bot_info->pickup_target = target;
+		self->bot_info->pickup_target_score = 1e-6f;
+		return true;
+	}
+	return false;
+}
+
+void cr_find_pickup_target( edict_t *self )
+{
+	edict_t *hit, *best=NULL;
+	vec3_t   orig;
+	float    min_dist, dd, imp;
+
+	if (self->bot_info->time_next_pickup>level.time) return;
+	self->bot_info->time_next_pickup = level.time + TIME_NEXT_PICKUP;
+
+	VectorCopy ( self->s.origin, orig );
+
+	min_dist = self->bot_info->pickup_target_score;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+		if (!hit->item || !hit->item->pickup) continue;
+		if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+		if (cr_check_unreachable(self,hit)) continue;
+
+		imp = cr_pickup_importance(self,hit);
+		if (imp<=0) continue;
+
+		dd = sqrt(sqr(orig[0]-hit->s.origin[0]) + sqr(orig[1]-hit->s.origin[1]) + sqr(orig[2]-hit->s.origin[2]));
+		dd /= imp;
+		if (dd>min_dist) continue;
+		if (!can_reach(self,hit)) continue;
+
+		// gi.bprintf ( PRINT_MEDIUM, "rated %s at %.2f\n", hit->classname, imp );
+
+		best = hit;
+		min_dist = dd;
+		if (min_dist<TOUCH_DIST) break;
+	}
+
+	if (best) {
+		self->bot_info->pickup_target = best;
+		self->bot_info->pickup_target_score = min_dist;
+		cr_set_move_target(self,self->bot_info->pickup_target->s.origin);
+	}
+}
+
+qboolean cr_find_pickup_urgent( edict_t *self )
+{
+	edict_t *hit, *best=NULL;
+	vec3_t   orig;
+	float    min_dist, dd, imp, pickup_range;
+	float    bottom_limit, top_limit;
+
+	if (self->bot_info->time_next_pickup>level.time) return false;
+	self->bot_info->time_next_pickup = level.time + TIME_NEXT_PICKUP;
+
+	VectorCopy ( self->s.origin, orig );
+	top_limit    = orig[2] + self->maxs[2]*0.9f;
+	bottom_limit = orig[2] + self->mins[2]*0.9f;
+
+	pickup_range = PICKUP_RANGE_MIN + (PICKUP_RANGE_MAX - PICKUP_RANGE_MIN)*0.1f*self->bot_pers->skill;
+	if (self->client->pers.weapon->weaponthink == Weapon_Blaster) pickup_range *= 3;
+
+	min_dist = 1e32;//self->bot_info->pickup_target_score;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+		if (!hit->item || !hit->item->pickup) continue;
+		if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+		if (cr_check_unreachable(self,hit)) continue;
+
+		if ( hit->s.origin[2]<bottom_limit || 
+			hit->s.origin[2]>top_limit ) continue;
+
+		imp = cr_pickup_importance(self,hit);
+		if (imp<=0) continue;
+
+		dd = sqrt(sqr(orig[0]-hit->s.origin[0]) + sqr(orig[1]-hit->s.origin[1]) + sqr(orig[2]-hit->s.origin[2]));
+		if (dd>pickup_range) continue;
+		dd /= imp;
+		// gi.bprintf ( PRINT_MEDIUM, "%s rated with %.2f score\n", hit->classname, dd );
+
+		if (dd>min_dist) continue;
+
+		if (!can_reach(self,hit)) continue;
+		// if (!can_reach(self,hit->s.origin)) continue;
+
+		best = hit;
+		min_dist = dd;
+		if (min_dist<TOUCH_DIST) break;
+	}
+
+	if (min_dist>pickup_range) return false;
+
+	if (best) {
+		// gi.bprintf ( PRINT_MEDIUM, "going for %s with %.2f score\n", best->classname, min_dist );
+		self->bot_info->pickup_target = best;
+		self->bot_info->pickup_target_score = min_dist;
+		cr_set_move_target( self, self->bot_info->pickup_target->s.origin );
+		return true;
+	}
+
+	return false;
+}
+
+
+qboolean cr_vertical_ok ( edict_t* self )
+{
+	return self->bot_info->next_node && 
+		( (self->bot_info->next_node->flags & NF_ELEVATOR) ||
+		(self->bot_info->next_node->flags & NF_LADDER) );
+}
+
+qboolean cr_wait_ok ( edict_t* self )
+{
+	return self->think==cr_think_wait  || self->think==cr_think_salute ||
+		self->think==cr_think_taunt || self->think==cr_think_flip ||
+		self->think==cr_think_attack ||
+		( self->bot_info->next_node && 
+		( (self->bot_info->next_node->flags & NF_DOOR) ||
+		(self->bot_info->next_node->flags & NF_ELEVATOR) ) );
+}
+
+qboolean cr_no_way( edict_t *self, vec3_t spot )
+{
+	float dd;
+
+	if (cr_vertical_ok(self)) return false;
+	if (!self->groundentity && spot[2]<self->s.origin[2]) return false;
+
+	dd = dist( self->s.origin, spot );
+	if (dd<TOUCH_DIST) return false;
+
+	return is_touched2d( self, spot );
+}
+
+void cr_crouch( edict_t* self, qboolean bCrouch )
+{
+	if (bCrouch) {
+		if (self->bot_info->b_crouch) return;
+
+		self->maxs[2] -= CROUCH_DELTA;
+		self->bot_info->b_crouch = true;
+	}
+	else {
+		if (!self->bot_info->b_crouch) return;
+
+		self->maxs[2] += CROUCH_DELTA;
+		self->bot_info->b_crouch = false;
+	}
+
+	// gi.linkentity(self);
+}
+
+
+qboolean cr_check_bottom( edict_t *self )
+{
+	vec3_t  mins, maxs, start, stop;
+	trace_t trace;
+	int     x, y, fail_count;
+	float   mid;
+
+	VectorAdd (self->s.origin, self->mins, mins);
+	VectorAdd (self->s.origin, self->maxs, maxs);
+
+	// if all of the points under the corners are solid world, don't bother
+	// with the tougher checks
+	// the corners must be within 16 of the midpoint
+	start[2] = mins[2] - 1;
+	for (x=0 ; x<=1 ; x++)
+		for (y=0 ; y<=1 ; y++) {
+			start[0] = x ? maxs[0] : mins[0];
+			start[1] = y ? maxs[1] : mins[1];
+			if (gi.pointcontents (start) != CONTENTS_SOLID)
+				goto realcheck;
+		}
+
+	return true;        // we got out easy
+realcheck:
+	//
+	// check it for real...
+	//
+	start[2] = mins[2];
+
+	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+	stop[2] = start[2] - (STEPSIZE + JUMP_HEIGHT);
+	trace = gi.trace( start, vec3_origin, vec3_origin, stop, self, MASK_PLAYERSOLID );
+
+	if (trace.fraction == 1.f) return false;
+	mid = trace.endpos[2];
+
+	// the corners must be within 16 of the midpoint    
+	fail_count=0;
+	for (x=0 ; x<=1 ; x++)
+		for (y=0 ; y<=1 ; y++) {
+			start[0] = stop[0] = x ? maxs[0] : mins[0];
+			start[1] = stop[1] = y ? maxs[1] : mins[1];
+
+			trace = gi.trace( start, vec3_origin, vec3_origin, stop, self, MASK_PLAYERSOLID );
+
+			if ( trace.fraction==1.f || (mid - trace.endpos[2]) > JUMP_HEIGHT ) {
+				fail_count++;
+				if (fail_count>2) return false;
+			}
+		}
+
+	return true;
+}
+
+qboolean cr_try_move ( edict_t *self, float d_yaw, qboolean bCheckFall )
+{
+	vec3_t   move, oldorg, start, end;
+	trace_t  trace, trace_lava;
+	float    d, yaw;
+
+	if (!self->groundentity) return true;
+
+	d = FRAMETIME*self->bot_pers->speed;
+	if (self->bot_info->b_crouch) d *= CROUCH_SPEED_COEF;
+	else if (self->waterlevel>1) d *= WATER_SPEED_COEF;
+
+	yaw = (self->ideal_yaw + d_yaw)*M_PI*2/360;
+
+	move[0] = d*cos(yaw);
+	move[1] = d*sin(yaw);
+	move[2] = 0;
+
+	VectorScale ( move, FRAMETIME, self->velocity );
+	VectorCopy ( self->velocity, self->movedir );
+
+	// try the move 
+	VectorCopy ( self->s.origin, oldorg );
+	VectorAdd ( self->s.origin, move, start );
+
+	VectorCopy ( start, end );
+	start[2] += STEPSIZE;
+	end[2] -= /*STEPSIZE +*/ JUMP_HEIGHT;
+
+	trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+
+	if (trace.allsolid) return false;
+
+	if (trace.startsolid) {
+		start[2] -= STEPSIZE;
+		trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+		if (trace.allsolid) return false;
+		if (trace.startsolid) {
+			if (self->bot_info->b_crouch) return false;
+			// can we crouch?!
+			trace = gi.trace ( start, self->mins, 
+				tv( self->maxs[0], self->maxs[1], self->maxs[2]-CROUCH_DELTA), 
+				end, self, MASK_PLAYERSOLID );
+			if (trace.allsolid || trace.startsolid) return false;
+			cr_crouch(self,true);
+		}
+	}
+
+	if (bCheckFall && !(trace.contents & CONTENTS_WATER)) {
+		if (trace.fraction == 1.f) return false; // walked off an edge
+		// check point traces down for dangling corners
+		VectorCopy ( trace.endpos, self->s.origin );
+
+		if (!cr_check_bottom(self)) {
+			VectorCopy ( oldorg, self->s.origin );
+			return false;
+		}
+	}
+
+	VectorScale ( move, 1.1f, move );
+	VectorAdd ( oldorg, move, start );
+	VectorCopy( start, end );
+	end[2] -= 300.f;
+	trace_lava = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME );
+	if (trace_lava.fraction>0.99f || trace_lava.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) {
+		VectorCopy ( oldorg, self->s.origin );
+		return false;
+	}
+
+	VectorCopy ( trace.endpos, self->s.origin );
+
+	self->groundentity = trace.ent;
+	self->groundentity_linkcount = trace.ent->linkcount;
+
+	/*!!!
+// double-check if inside something
+    VectorCopy ( self->s.origin, start );
+    VectorCopy ( start, end );
+    end[2] += 0.25f;
+
+    trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+
+    if (trace.allsolid || trace.startsolid) {
+      VectorCopy ( oldorg, self->s.origin );
+      return false; 
+      }
+*/
+
+	self->ideal_yaw = anglemod( self->ideal_yaw + d_yaw );
+
+	// the move is ok
+	gi.linkentity (self);
+
+	return true;
+}
+
+qboolean cr_can_stand( edict_t* self )
+{
+	vec3_t  start, end;
+	trace_t trace;
+
+	if (!self->bot_info->b_crouch) return false;
+	if ( self->bot_info->time_next_crouch > level.time ) return false;
+	self->bot_info->time_next_crouch = level.time + TIME_CROUCH;
+
+	VectorCopy ( self->s.origin, start );
+	VectorCopy ( start, end );
+	end[2] += 0.25f;
+	// end[2] += CROUCH_DELTA;
+
+	self->maxs[2] += CROUCH_DELTA;
+	trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	self->maxs[2] -= CROUCH_DELTA;
+
+	if (trace.startsolid || trace.allsolid || trace.fraction<0.99) return false;
+
+	return true;
+}
+
+void cr_jump( edict_t *self )
+{
+	if (!self->groundentity) return;
+	if (self->bot_info->b_crouch || self->bot_info->b_on_ladder) return;
+
+	YawVector( self->ideal_yaw, self->velocity );
+	self->velocity[0] *= BOT_MAX_SPEED;
+	self->velocity[1] *= BOT_MAX_SPEED;
+	self->velocity[2] = BOT_JUMP_SPEED;
+	VectorCopy( self->velocity, self->movedir );
+	self->groundentity = NULL;
+	gi.sound( self, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+	self->s.frame = FRAME_jump1;
+}
+
+qboolean cr_can_jump ( edict_t *self )
+{
+	vec3_t  move, neworg, start, end, oldorg;
+	trace_t trace, trace_lava;
+	float   cs, sn, yaw;
+
+	if (self->bot_info->b_crouch || self->bot_info->b_on_ladder) return false;
+	if (self->bot_info->time_next_jump>level.time) return false;
+
+	yaw = self->ideal_yaw*M_PI*2/360;
+	cs = cos(yaw);
+	sn = sin(yaw);
+
+	move[0] = MOVE_JUMP_DIST*cs;
+	move[1] = MOVE_JUMP_DIST*sn;
+	move[2] = 0;
+
+	// try the move 
+	VectorAdd ( self->s.origin, move, neworg );
+
+	VectorCopy ( neworg, end );
+	neworg[2] += JUMP_HEIGHT;
+	end[2]    -= STEPSIZE;
+
+	trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+
+	if (trace.allsolid || trace.startsolid) return false;
+
+	neworg[0] -= move[0];
+	neworg[1] -= move[1];
+
+	end[0] = neworg[0] + JUMP_DIST*cs;
+	end[1] = neworg[1] + JUMP_DIST*sn;
+	end[2] = neworg[2];
+
+	trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID | CONTENTS_LADDER );
+	if (trace.allsolid || trace.startsolid) return false;
+	if (trace.contents & CONTENTS_LADDER) return true;
+
+	VectorCopy ( trace.endpos, neworg );
+	VectorCopy ( neworg, end );
+	end[2] -= 300.f;
+	trace = gi.trace ( neworg, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	if (trace.allsolid || trace.startsolid || trace.fraction>0.99f) return false;
+
+	if ( trace.endpos[2]<(self->bot_info->move_target[2]-STEPSIZE) ) return false;
+
+	// check for solid ground
+	VectorCopy ( self->s.origin, oldorg );
+	VectorCopy ( trace.endpos, self->s.origin );
+	if (!cr_check_bottom(self)) {
+		VectorCopy ( oldorg, self->s.origin );
+		return false;
+	}
+	VectorCopy ( oldorg, self->s.origin );
+
+	VectorCopy( trace.endpos, start );
+	VectorCopy( start, end );
+	end[2] -= 300.f;
+	trace_lava = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME  );
+	if (trace_lava.fraction>0.99f || trace_lava.contents & (CONTENTS_LAVA|CONTENTS_SLIME)) return false;
+
+	return true;
+}
+
+void cr_effect( edict_t *self, int eff )
+{
+	gi.WriteByte  ( svc_muzzleflash );
+	gi.WriteShort ( self - g_edicts );
+	gi.WriteByte  ( eff );
+	gi.multicast  ( self->s.origin, MULTICAST_PVS );
+}
+
+qboolean cr_try_rocketjump( edict_t *self )
+{
+	edict_t* pt;
+	float    d, max_importance;
+	vec3_t   move, start, end, oldorg, aim;
+	trace_t  trace;
+	int      armor_index;
+
+	if ( !self->groundentity || self->bot_info->b_crouch || 
+		self->bot_info->time_next_jump>level.time) return false;
+	if ( self->client->invincible_framenum < level.framenum &&
+		(self->health<80.f || self->client->breather_framenum > level.framenum) ) return false;
+
+	if ( self->bot_info->time_next_shot > (level.time-TIME_WEAPON_CHANGE) ||
+		self->client->pers.inventory[INDEX_ROCKETS]<1 ||
+		self->client->pers.inventory[INDEX_ROCKET_LAUNCHER]<1 ) return false;
+
+	pt = self->bot_info->pickup_target;
+	if ( !pt || pt->s.origin[2]<(self->s.origin[2]+JUMP_HEIGHT) ||
+		pt->s.origin[2]>(self->s.origin[2]+ROCKETJUMP_HEIGHT) ) return false;
+
+	d = dist2d2(pt->s.origin,self->s.origin);
+	if ( d>ROCKETJUMP_MAXDIST*ROCKETJUMP_MAXDIST || 
+		d<ROCKETJUMP_MINDIST*ROCKETJUMP_MINDIST) return false;
+
+	armor_index = ArmorIndex(self);
+	if (!armor_index) max_importance = 600;
+	else max_importance = 600 - 4*self->client->pers.inventory[armor_index];
+
+	if (cr_pickup_importance(self,pt)<max_importance) return false;
+
+	VectorSubtract( pt->s.origin, self->s.origin, move );
+	move[2]=0;
+	VectorNormalize( move );
+	VectorScale( move, ROCKETJUMP_DIST, move );
+
+	VectorCopy( self->s.origin, start );
+	VectorAdd( start, move, end );
+	end[2] += ROCKETJUMP_HEIGHT;
+
+	// cr_draw_line( start, end, COLOR_BEAM_GREEN, &global_path_route );
+
+	trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	if (trace.allsolid || trace.startsolid) return false;
+
+	if (trace.fraction==1.f) {
+		// continue the jump
+		VectorCopy( end, start );
+		VectorAdd( start, move, end );
+		end[2] -= ROCKETJUMP_HEIGHT-JUMP_HEIGHT;
+
+		// cr_draw_line( start, end, COLOR_BEAM_GREEN, &global_path_route );
+
+		trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+		if ( trace.allsolid || trace.startsolid || trace.fraction>0.9f ) return false;
+	}
+
+	if (trace.endpos[2]<(pt->s.origin[2]-STEPSIZE)) return false;
+
+	// cast a ray down
+	VectorCopy( trace.endpos, start );
+	VectorCopy( start, end );
+	end[2] = pt->s.origin[2]-JUMP_HEIGHT;
+
+	// cr_draw_line( start, end, COLOR_BEAM_BLUE, &global_path_route );
+
+	trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME );
+	if ( trace.allsolid || trace.startsolid || trace.fraction==1.f || 
+		trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME) ) return false;
+
+	VectorCopy ( self->s.origin, oldorg );
+	VectorCopy ( trace.endpos, self->s.origin );
+	if (!cr_check_bottom(self)) {
+		VectorCopy ( oldorg, self->s.origin );
+		return false;
+	}
+	VectorCopy ( oldorg, self->s.origin );
+
+	// victory!
+	VectorCopy ( pt->s.origin, self->bot_info->move_target );
+	self->ideal_yaw = vectoyaw(move);
+	cr_jump(self);
+
+	// fire a rocket down below!
+	VectorSet( aim, 0,0,-1);
+	fire_rocket ( self, self->s.origin, aim, 
+		ROCKET_DAMAGE, ROCKET_SPEED, 120/*damage_rad*/, ROCKET_DAMAGE/*radius_damage*/ );
+	cr_effect( self, MZ_ROCKET );
+	self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE;
+	self->client->pers.inventory[INDEX_ROCKETS]--;
+
+	return true;
+}
+
+qboolean cr_try_jump ( edict_t *self, float d_yaw )
+{
+	float oldyaw;
+
+	oldyaw = self->ideal_yaw;
+	self->ideal_yaw = anglemod( self->ideal_yaw + d_yaw );
+
+	if (cr_can_jump(self)) {
+		cr_jump(self);
+		return true;
+	}
+
+	self->ideal_yaw = oldyaw;
+
+	return false;
+}
+
+qboolean cr_move_avoid ( edict_t *self, qboolean bSafe )
+{
+	qboolean bRes = true;
+
+	if (!self->groundentity) return false;
+
+	if (!bSafe && self->bot_info->b_on_platform) bSafe = true;
+
+	if (self->bot_pers->skill>3) cr_avoid_rocket(self);
+	if (self->bot_pers->skill>5 && cr_try_rocketjump(self)) return false;
+
+	// jump on ladder
+	if ( self->bot_info->next_node && (self->bot_info->next_node->flags & NF_LADDER) ) {
+		if ( dist2d2( self->s.origin, self->bot_info->next_node->position ) < sqr(JUMP_DIST*0.6f) ) {
+			cr_jump(self);
+			return false;
+		}
+	}
+
+	if (!cr_try_move( self, 0, bSafe )) {
+		float dy;
+		qboolean bReverse;
+
+		if (cr_try_jump( self, 0 )) return false;
+
+		bRes = false;
+		bReverse = qrandom()>0.5f;
+		for ( dy=30; ; dy+=30 ) {
+			if (bReverse) dy = -dy;
+			if (cr_try_move( self,  dy, bSafe )) break;
+			if (cr_try_jump( self,  dy )) return false;
+			if (cr_try_move( self, -dy, bSafe )) break;
+			if (cr_try_jump( self, -dy )) return false;
+			if (bReverse) dy = -dy;
+			if (dy>90) break;
+		}
+	}
+
+	if (self->bot_info->b_crouch && cr_can_stand( self )) {
+		cr_crouch(self,false);
+	}
+
+	return bRes;
+}
+
+qboolean cr_swim( edict_t *self )
+{
+	vec3_t  start, end, normal, velocity, end_pos;
+	trace_t trace;
+	int     x, y;
+	float   d;
+
+	VectorSubtract ( self->bot_info->move_target, self->s.origin, self->velocity );
+	VectorNormalize ( self->velocity );
+	VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
+	VectorCopy ( self->velocity, velocity );
+
+	VectorMA( self->s.origin, FRAMETIME, velocity, end_pos );
+
+	// check if we can go where we want
+	VectorCopy( self->s.origin, start );
+	VectorCopy( end_pos, end );
+	trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	if (!trace.allsolid && !trace.startsolid && trace.fraction==1.f) {
+		// check that we are not leaving water by this move
+		end[2] += self->mins[2];
+		if ( !(gi.pointcontents(end) & (CONTENTS_WATER|CONTENTS_SOLID)) ) {
+			self->velocity[2] = 0;
+			VectorNormalize ( self->velocity );
+			VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
+			return false;
+		}
+		return true;
+	}
+	VectorCopy ( trace.plane.normal, normal );
+
+	// check if we can go out of water
+	VectorCopy( end_pos, end );
+	VectorCopy( end, start );
+	start[2] += 2*STEPSIZE;
+
+	trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	if (trace.startsolid) {
+		start[2] -= STEPSIZE;
+		trace = gi.trace( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+	}
+	if ( !trace.startsolid && !trace.allsolid && trace.fraction<0.99f &&
+		!(gi.pointcontents(trace.endpos) & CONTENTS_WATER) ) {
+		// yes, we can get out of the water!
+		VectorCopy ( trace.endpos, self->s.origin );
+		gi.linkentity(self);
+		return true;
+	}
+
+	d = DotProduct( normal, velocity );
+	if ( fabs(d)>0.9f*self->bot_pers->speed*WATER_SPEED_COEF ) {
+		vec3_t right, up, angles;
+
+		VectorCopy( end_pos, end );
+
+		vectoangles ( velocity, angles );
+		AngleVectors ( angles, NULL, right, up );
+
+		VectorScale( right, (fabs(self->maxs[0])+fabs(self->maxs[0]))/2, right );
+		VectorScale( up, (fabs(self->maxs[2])+fabs(self->maxs[2]))/2, up );
+		for ( x=-1; x<=1; x++ )
+			for ( y=-1; y<=1; y++ ) {
+				if ( x==0 && y==0 ) continue;
+				VectorCopy( end, start );
+				if (x) VectorMA( start, x, right, start );
+				if (y) VectorMA( start, y, up, start );
+				if ( !(gi.pointcontents(start) & CONTENTS_SOLID) ) {
+					VectorSubtract ( end, self->s.origin, self->velocity );
+					VectorMA ( self->velocity, y, up, self->velocity );
+					VectorNormalize ( self->velocity );
+					VectorScale ( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
+					return true;
+				}
+			}
+	}
+
+	VectorMA( velocity, -0.6f*d, normal, self->velocity );
+	VectorNormalize( self->velocity );
+	VectorScale( self->velocity, self->bot_pers->speed*WATER_SPEED_COEF, self->velocity );
+
+	return false;
+}
+
+qboolean cr_move( edict_t *self, qboolean bFacing, qboolean bSafe )
+{
+	qboolean bStuck;
+
+	if (bFacing) M_ChangeYaw (self);
+
+	if (self->bot_info->b_on_ladder) {
+		vec3_t  start, end;
+		trace_t trace;
+
+		VectorSubtract ( self->bot_info->move_target, self->s.origin, self->velocity );
+		VectorNormalize( self->velocity );
+		VectorScale( self->velocity, 200, self->velocity );
+		VectorCopy ( self->velocity, self->movedir );
+
+		VectorCopy ( self->s.origin, start );
+		VectorCopy ( start, end );
+		end[2] += STEPSIZE;
+		trace = gi.trace ( start, self->mins, self->maxs, end, self, MASK_PLAYERSOLID );
+		if (!trace.allsolid && !trace.startsolid && trace.fraction==1.f) {
+			VectorCopy ( end, self->s.origin );
+			gi.linkentity( self );
+			self->bot_info->time_last_stuck=0;
+			return true;
+		}
+		// we're stuck!
+		if (!self->bot_info->time_last_stuck) {
+			self->bot_info->time_last_stuck=level.time;
+			return true;
+		}
+		return (level.time - self->bot_info->time_last_stuck)<3*TIME_STUCK;
+	}
+
+	if ( self->waterlevel>2 || 
+		(self->waterlevel && !self->groundentity)) {
+		if (self->bot_info->b_crouch) cr_crouch(self,false);
+		bStuck = !cr_swim(self);
+		if (bStuck) {
+			if (!self->bot_info->time_last_stuck) {
+				self->bot_info->time_last_stuck=level.time;
+				return true;
+			}
+			return (level.time - self->bot_info->time_last_stuck)<3*TIME_STUCK;
+		}
+		self->bot_info->time_last_stuck=0;
+		return true;
+	}
+
+	if (!self->groundentity) {
+		if ( is_touched2d( self, self->bot_info->move_target ) ) {
+			VectorClear(self->movedir);
+		}
+		self->velocity[0] = self->movedir[0];
+		self->velocity[1] = self->movedir[1];
+		return true;
+	}
+	// we hit the slope!
+	if (self->bot_info->b_on_slope) {
+		self->bot_info->b_on_slope = false;
+		self->bot_info->time_last_stuck = 0;
+		return false;
+	}
+
+	bStuck = !cr_move_avoid( self, bSafe );
+
+	if (bStuck) {
+		self->bot_info->move_block_count=0;
+		// can't move
+		if (!self->bot_info->time_last_stuck) {
+			self->bot_info->time_last_stuck=level.time;
+			return true;
+		}
+		else
+			if ( (level.time - self->bot_info->time_last_stuck) > 
+				(cr_vertical_ok(self) ? 10*TIME_STUCK : (cr_wait_ok(self) ? 5*TIME_STUCK : TIME_STUCK) ) ) {
+				// gi.bprintf ( PRINT_MEDIUM, "%s reporting stuck!\n", self->client->pers.netname );
+				self->bot_info->time_last_stuck=0;
+				return false;
+			}
+			else
+				return true;
+	}
+
+	if (self->bot_info->move_block_count>1)
+		self->bot_info->time_last_stuck=0;
+	else 
+		self->bot_info->move_block_count++;
+	return true;
+}
+
+qboolean cr_moveto( edict_t *self )
+{
+	vec3_t move;
+	float  dt;
+
+	VectorSubtract ( self->bot_info->move_target, self->s.origin, move );
+
+	if (!is_closer_b( self->bot_info->move_target, self->bot_info->last_move_target, 1 )) {
+		VectorCopy ( self->bot_info->move_target, self->bot_info->last_move_target );
+		dt =  0.5f + 1.2f*VectorLength(move)/self->bot_pers->speed;
+		if (self->waterlevel>1) dt /= WATER_SPEED_COEF;
+		else if (self->bot_info->b_crouch) dt /= CROUCH_SPEED_COEF;
+		self->bot_info->time_last_move_target = level.time + dt;
+		// gi.dprintf( "%s has %.1f sec to cover %.1f distance!\n", self->client->pers.netname, dt, VectorLength(move) );
+	}
+	else {
+		if (level.time > self->bot_info->time_last_move_target) {
+			// gi.dprintf( "time is up for %s!\n", self->client->pers.netname );
+			return false;
+		}
+	}
+
+	self->ideal_yaw = vectoyaw(move);
+	return cr_move( self, true, (self->bot_info->move_target[2]-self->s.origin[2])>-STEPSIZE );
+}
+
+qboolean cr_move_yaw( edict_t *self, qboolean bFacing, qboolean bSafe )
+{
+	vec3_t move;
+
+	YawVector( self->ideal_yaw, move );
+	VectorMA( self->s.origin, 80, move, self->bot_info->move_target );
+	self->bot_info->move_target[2] += STEPSIZE;
+	if (self->waterlevel==1) self->bot_info->move_target[2] -= 2*STEPSIZE;
+	return cr_move( self, bFacing, bSafe );
+}
+
+void cr_update_enemy( edict_t *self )
+{
+	if (!self->enemy) return;
+
+	if ( !self->enemy->inuse ||
+		self->enemy->deadflag>=DEAD_DYING || !visible(self,self->enemy)) {
+		// we lost him!  
+		cr_forget_enemy(self);
+		return;
+	}
+
+	VectorCopy( self->enemy->s.origin, self->monsterinfo.last_sighting );
+	self->monsterinfo.trail_time = level.time;
+}
+
+void cr_try_attack( edict_t *self )
+{
+	if (!self->enemy) return;
+
+	if ( distance(self, self->enemy)<self->bot_pers->attack_range &&
+		fabs(self->s.origin[2]-self->enemy->s.origin[2])<(self->bot_pers->attack_range*0.5f) ) {
+		self->think = cr_think_attack;
+	}
+}
+
+void cr_check_ground( edict_t *self )
+{
+	vec3_t      point;
+	trace_t     trace;
+	qboolean    was_on_ladder;
+
+	self->bot_info->b_on_platform = false;
+	was_on_ladder = self->bot_info->b_on_ladder;
+	self->bot_info->b_on_ladder = false;
+
+	if (self->velocity[2] > 200) {
+		self->groundentity = NULL;
+		return;
+	}
+
+	// check if we are on ladder
+	if ( self->deadflag!=DEAD_DEAD && 
+		self->bot_info->move_target[2]>self->s.origin[2] ) {
+		vec3_t  start, end;
+
+		if (!was_on_ladder) YawVector( self->ideal_yaw, self->bot_info->ladder_dir );
+		VectorCopy( self->s.origin, start );
+		VectorMA ( start, was_on_ladder ? 60 : 50, self->bot_info->ladder_dir, end );
+		trace = gi.trace ( start, self->mins, self->maxs, end, self, CONTENTS_LADDER | CONTENTS_SOLID );
+		if ( trace.contents & CONTENTS_LADDER ) {
+			self->bot_info->b_on_ladder = true;
+			if (!was_on_ladder) {
+				// face the ladder!
+				VectorScale ( trace.plane.normal, -1, self->bot_info->ladder_dir );
+			}
+			// gi.dprintf("%.1f %.1f %.1f \n", trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2] );
+		}
+		else {
+			if (was_on_ladder) {
+				VectorScale( self->bot_info->ladder_dir, 150, self->velocity );
+				self->velocity[2] = 250;
+				VectorCopy ( self->velocity, self->movedir );
+			}
+		}
+	}
+
+	// if the hull point one-quarter unit down is solid the entity is on ground
+	point[0] = self->s.origin[0];
+	point[1] = self->s.origin[1];
+	point[2] = self->s.origin[2] - 0.25;
+
+	trace = gi.trace ( self->s.origin, self->mins, self->maxs, point, self, MASK_PLAYERSOLID );
+
+	// check steepness
+	if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) {
+		self->groundentity = NULL;
+
+		if (trace.plane.normal[0]!=0 || trace.plane.normal[1]!=0) {
+			VectorScale( trace.plane.normal, 100, self->velocity );
+			VectorCopy ( self->velocity, self->movedir );
+			self->bot_info->b_on_slope = true;
+		}
+		return;
+	}
+
+	if (!trace.startsolid && !trace.allsolid) {
+		VectorCopy (trace.endpos, self->s.origin);
+		self->groundentity = trace.ent;
+		self->groundentity_linkcount = trace.ent->linkcount;
+		self->velocity[2] = 0;
+	}
+
+	if (self->groundentity && self->groundentity->blocked)
+		self->bot_info->b_on_platform = true;
+
+}
+
+void cr_skip_enemy( edict_t *self )
+{
+	if (!self->enemy) return;
+	cr_add_unreachable( self, self->enemy, UNREACHABLE_ENEMY_TIMEOUT );
+	cr_forget_enemy(self);
+}
+
+void cr_skip_pickup_target( edict_t *self )
+{
+	if (self->bot_info->pickup_target) {
+		cr_add_unreachable( self, self->bot_info->pickup_target, 
+			strcmp( self->bot_info->pickup_target->classname, "item_flag_team1" )==0 ||
+			strcmp( self->bot_info->pickup_target->classname, "item_flag_team2" )==0 ?
+			UNREACHABLE_TIMEOUT/3 : UNREACHABLE_TIMEOUT );
+	}
+	cr_forget_pickup_target( self );
+}
+
+void cr_do_physics( edict_t* self )
+{
+	cr_check_ground (self);
+
+	if (self->bot_info->b_airborn && self->groundentity) {
+		self->bot_info->time_next_jump = level.time + JUMP_DELAY;
+	}
+	self->bot_info->b_airborn = !self->groundentity;
+}
+
+void cr_catagorize_position (edict_t *self)
+{
+	vec3_t      point;
+	int         cont;
+
+	// get waterlevel
+	point[0] = self->s.origin[0];
+	point[1] = self->s.origin[1];
+	point[2] = self->s.origin[2] + self->mins[2] + 1;
+	cont = gi.pointcontents (point);
+
+	if ( !(cont & CONTENTS_SOLID) )
+		self->bot_info->time_next_solid = level.time+1.f;
+	else {
+		if ( self->bot_info->time_next_solid < level.time ) {
+			self->bot_info->time_next_solid = level.time+1.f;
+			T_Damage ( self, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, 0, MOD_SUICIDE );
+			// gi.dprintf( "%s is in solid!\n", self->client->pers.netname );
+			// FIXME: push bot along normal!
+			if (cont & MASK_WATER) {
+				self->s.origin[2] -= 0.2f;
+				gi.linkentity(self);
+			}
+		}
+	}
+
+	if (!(cont & MASK_WATER)) {
+		self->waterlevel = 0;
+		self->watertype = 0;
+		return;
+	}
+
+	self->watertype = cont;
+	self->waterlevel = 1;
+	point[2] += 24;
+	cont = gi.pointcontents (point);
+	if (!(cont & MASK_WATER)) return;
+
+	self->waterlevel = 2;
+	point[2] += 22;
+	cont = gi.pointcontents (point);
+	if (cont & MASK_WATER) self->waterlevel = 3;
+}
+
+void cr_update_environment( edict_t *self )
+{
+	cr_do_physics(self);
+	cr_catagorize_position (self);
+}
+
+void cr_check_stuck( edict_t *self )
+{
+	if ( level.time < self->bot_info->time_stuck_check ) return;
+
+	if ( is_closer_b( self->s.origin, self->bot_info->old_origin, 5 ) &&
+		!cr_vertical_ok(self) && !cr_wait_ok(self)) {
+		self->ideal_yaw = 360*qrandom();
+		YawVector( self->ideal_yaw, self->velocity );
+		self->bot_info->stuck_count++;
+		if (self->bot_info->stuck_count>4) cr_jump(self);
+	}
+	else 
+		self->bot_info->stuck_count=0;
+
+	if (self->bot_info->stuck_count>8) {
+		T_Damage ( self, self, self, vec3_origin, self->s.origin, vec3_origin, 20, 0, 0, MOD_SUICIDE );
+		self->bot_info->stuck_count=0;
+		// gi.bprintf( PRINT_MEDIUM, "%s punished at %.2f %.2f %.2f\n", self->client->pers.netname, self->s.origin[0], self->s.origin[1], self->s.origin[2] );
+	}
+
+	VectorCopy ( self->s.origin, self->bot_info->old_origin );
+	self->bot_info->time_stuck_check = level.time + TIME_CHECK_STUCK;
+}
+
+qboolean cr_update( edict_t *self, qboolean bTryEnemy )
+{
+	if (!self->inuse) return false;
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->bot_pers->b_adapting) {
+		if (self->bot_pers->adapt_count<-1 && self->bot_pers->skill>1) {
+			self->bot_pers->skill--;
+			cr_compute_skills(self,self->bot_pers->skill);
+		}
+		if (self->bot_pers->adapt_count>1 && self->bot_pers->skill<10) {
+			self->bot_pers->skill++;
+			cr_compute_skills(self,self->bot_pers->skill);
+		}
+	}
+
+	cr_update_environment(self);
+
+	if (bTryEnemy) {
+		cr_update_enemy(self);
+		if ( cr_low_health(self) ) {
+			if ( self->think==cr_think_attack ||
+				self->think==cr_think_chase ||
+				self->think==cr_think_chase_route ) {
+				self->think = cr_think_run_for_life;
+			}
+		}
+		else {
+			cr_try_attack(self);
+		}
+	}
+
+	cr_update_routes(self);
+	cr_check_stuck(self);
+
+	return true;
+}
+
+void cr_fire_and_run( edict_t *self )
+{
+	qboolean bFire;
+
+	bFire = false;
+	//FIXME: face the enemy!
+	if (self->enemy) {
+		if (cr_infront(self,self->enemy))
+			bFire = cr_fire_weapon( self, self->enemy );
+	}
+	else {
+		edict_t *enemy, *best_enemy=NULL;
+		float    d, best_dist=1e10;
+		// do a breaf search for enemy
+		for ( enemy=g_edicts+1; enemy<=(g_edicts+game.maxclients); enemy++) {
+			if (!enemy->inuse || enemy==self || (enemy->flags & FL_NOTARGET)) continue;
+
+			// attack only bots from other teams, unless we are in team #0
+			if ( self->client->pers.team_no>0 && 
+				enemy->client->pers.team_no==self->client->pers.team_no ) continue;
+
+			// is it dead already?
+			if ( enemy->deadflag>DEAD_DYING || (enemy->svflags & SVF_NOCLIENT) ) continue;
+
+			// can we see him?
+			if (!cr_infront (self,enemy)) continue;
+			if (!can_reach(self,enemy)) continue;
+
+			d = dist2(self->s.origin,enemy->s.origin);
+			if (d>ATTACK_RANGE_MAX*ATTACK_RANGE_MAX) continue;
+
+			if (!best_enemy || d<best_dist ) {
+				best_enemy = enemy;
+				best_dist = d;
+				if (best_dist<MELEE_COMBAT_DIST*MELEE_COMBAT_DIST) break;
+			}
+		}
+		if (best_enemy) {
+			bFire = cr_fire_weapon( self, best_enemy );
+		}
+	}
+
+	if (bFire) {
+		self->client->anim_end = self->s.frame;
+		if (!self->bot_info->b_crouch) self->s.frame = FRAME_attack1;
+		else self->s.frame = FRAME_crattak1;
+	}
+	else {
+		if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
+		else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
+	}
+}
+
+void cr_post_think( edict_t *self )
+{
+	int      i;
+	edict_t* other;
+
+	if ( !self->bot_info->b_shot_this_frame &&
+		self->bot_info->time_stop_shoting > level.time ) {
+		cr_fire_weapon( self, NULL );
+	}
+	self->bot_info->b_shot_this_frame = false;
+
+	VectorCopy ( self->s.angles, self->client->ps.viewangles);
+	VectorCopy ( self->s.angles, self->client->v_angle);
+
+	// turn on powerups
+	if (!(self->flags & FL_POWER_ARMOR) && 
+		(self->client->pers.inventory[INDEX_POWER_SCREEN]>0 || self->client->pers.inventory[INDEX_POWER_SHIELD]>0) &&
+		self->client->pers.inventory[INDEX_CELLS]>0 ) {
+		self->flags |= FL_POWER_ARMOR;
+		gi.sound( self, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0 );
+	}
+	if ( self->client->quad_framenum<level.framenum &&
+		self->client->pers.inventory[INDEX_QUAD]>0 ) {
+		gitem_t   *item;
+		item = FindItem("Quad Damage");
+		if (item && item->use) item->use( self, item );
+	}
+	if ( self->client->invincible_framenum<level.framenum &&
+		self->client->pers.inventory[INDEX_INVULN]>0 ) {
+		gitem_t   *item;
+		item = FindItem("Invulnerability");
+		if (item && item->use) item->use( self, item );
+	}
+	if ( self->waterlevel>2 &&
+		self->client->breather_framenum<level.framenum &&
+		self->client->pers.inventory[INDEX_BREATHER]>0 ) {
+		gitem_t   *item;
+		item = FindItem("Rebreather");
+		if (item && item->use) item->use( self, item );
+	}
+
+	// CTF stuff
+	if (self->client->ctf_grapple) {
+		CTFGrapplePull(self->client->ctf_grapple);
+		gi.linkentity(self);
+	}
+	CTFApplyRegeneration(self);
+	for (i = 1; i <= maxclients->value; i++) {
+		other = g_edicts + i;
+		if (other->inuse && other->client->chase_target == self)
+			UpdateChaseCam(other);
+	}
+}
+
+qboolean cr_try_pickup_urgent ( edict_t *self )
+{
+	if ( self->enemy && 
+		self->enemy->health>0 && self->enemy->health<50 ) return false;
+
+	if (!cr_find_pickup_urgent(self)) return false;
+
+	self->bot_info->old_think = self->think;
+	self->think = cr_think_temp_target;
+
+	self->bot_info->last_node=NULL;
+	self->bot_info->next_node=NULL;
+
+	// gi.bprintf ( PRINT_MEDIUM, "urgently going for %s\n", self->bot_info->pickup_target->classname );
+
+	return true;
+}
+
+void cr_think_team_help( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+	if (self->bot_info->bot_assignment!=ASSN_HELP) {
+		self->think = cr_think;
+		return;
+	}
+
+	// if (cr_try_pickup_urgent(self)) return;
+	cr_find_enemy(self);
+
+	if ( level.time>self->bot_info->time_next_chase_update ) {
+		self->bot_info->time_next_chase_update = level.time + 0.5f;
+		if ( cr_find_closest_node( self->bot_info->team_leader ) != 
+			self->bot_info->target_node ) {
+			if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
+				cr_dismiss(self);
+				self->think = cr_think;
+			}
+		}
+	}
+
+	//FIXME: dodge while following the path
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			cr_dismiss(self);
+			self->think = cr_think;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			// let's find detour!
+			if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
+				cr_dismiss(self);
+				self->think = cr_think;
+			}
+		}
+
+	// check if we are near our destination
+	if (self->bot_info->team_leader) {
+		if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
+			cr_dismiss(self);
+			if (self->enemy) self->think = cr_think_attack;
+			else self->think = cr_think_salute;
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_team_group( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+	if (self->bot_info->bot_assignment!=ASSN_GROUP) {
+		self->think = cr_think;
+		return;
+	}
+
+	// if (cr_try_pickup_urgent(self)) return;
+	cr_find_enemy(self);
+
+	if ( level.time>self->bot_info->time_next_chase_update ) {
+		self->bot_info->time_next_chase_update = level.time + 0.5f;
+		if ( cr_find_closest_node( self->bot_info->team_leader ) != 
+			self->bot_info->target_node ) {
+			if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
+				self->bot_info->target_node = NULL;
+				self->think = cr_think;
+			}
+		}
+	}
+
+	//FIXME: dodge while following the path
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			self->bot_info->target_node = NULL;
+			self->think = cr_think;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			// let's find detour!
+			if (!cr_find_route( self, self->bot_info->team_leader->s.origin, false )) {
+				self->bot_info->target_node = NULL;
+				self->think = cr_think;
+			}
+		}
+
+	// check if we are near our destination
+	if (self->bot_info->team_leader) {
+		if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
+			if (self->enemy) self->think = cr_think_attack;
+			else self->think = cr_think_salute;
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_wait( edict_t *self )
+{
+	if (!cr_update(self,false)) return;
+
+	self->bot_info->time_last_stuck=0;
+	self->bot_info->stuck_count=0;
+
+	// scan around
+	self->ideal_yaw = anglemod( self->ideal_yaw + self->bot_info->strafe_dir*100*FRAMETIME );
+	M_ChangeYaw(self);
+
+	if (level.time > self->bot_info->time_last_strafe_switch) {
+		self->bot_info->time_last_strafe_switch = level.time + 2.f+qrandom();
+		self->bot_info->strafe_dir = -self->bot_info->strafe_dir;
+	}
+
+	if (cr_find_enemy(self))
+		cr_sight(self,self->enemy);
+	else
+		if (self->bot_info->bot_assignment==ASSN_GROUP) {
+			if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) > 0x5000 ) {
+				self->think = cr_think_team_group;
+			}
+		}
+		else 
+			if (self->bot_info->bot_assignment==ASSN_NONE) {
+				self->think = cr_think;
+			}
+
+	if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_stand01, FRAME_stand40 );
+	else cr_animate_frames( self, FRAME_crstnd01, FRAME_crstnd19 );
+}
+
+void cr_think_team_patrol( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+	if (self->bot_info->bot_assignment!=ASSN_PATROL) {
+		self->think = cr_think;
+		return;
+	}
+
+	if (cr_try_pickup_urgent(self)) return;
+	cr_find_enemy(self);
+
+	if ( level.time>self->bot_info->time_next_chase_update ) {
+		self->bot_info->time_next_chase_update = level.time + 0.5f;
+		if ( cr_find_closest_node_pos( self, self->bot_info->bot_anchor ) != 
+			self->bot_info->target_node ) {
+			if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
+				cr_dismiss(self);
+				self->think = cr_think;
+			}
+		}
+	}
+
+	//FIXME: dodge while following the path
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			self->bot_info->target_node = NULL;
+			self->think = cr_think;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			// let's find detour!
+			if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
+				cr_dismiss(self);
+				self->think = cr_think;
+			}
+		}
+
+	// check if we are near our destination
+	if (self->bot_info->team_leader) {
+		if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
+			if (self->enemy) self->think = cr_think_attack;
+			else self->think = cr_think_salute;
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_team_guard( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+	if (self->bot_info->bot_assignment!=ASSN_GUARD) {
+		self->think = cr_think;
+		return;
+	}
+
+	// if (cr_try_pickup_urgent(self)) return;
+	cr_find_enemy(self);
+
+	if ( level.time>self->bot_info->time_next_chase_update ) {
+		self->bot_info->time_next_chase_update = level.time + 0.5f;
+		if ( cr_find_closest_node_pos( self, self->bot_info->bot_anchor ) != 
+			self->bot_info->target_node ) {
+			if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
+				// got to the point!
+				self->think = cr_think_salute;
+				self->bot_info->target_node = NULL;
+			}
+		}
+	}
+
+	//FIXME: dodge while following the path
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			// got to the point!
+			self->think = cr_think_salute;
+			self->bot_info->target_node = NULL;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			// let's find detour!
+			if (!cr_find_route( self, self->bot_info->bot_anchor, false )) {
+				// got to the point!
+				self->think = cr_think_salute;
+				self->bot_info->target_node = NULL;
+			}
+		}
+
+	// check if we are near our destination
+	if (self->bot_info->team_leader) {
+		if ( (int)dist2(self->s.origin,self->bot_info->team_leader->s.origin) < (rand()&0x2FFF) ) {
+			if (self->enemy) self->think = cr_think_attack;
+			else self->think = cr_think_salute;
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_temp_target( edict_t *self )
+{
+	if (!cr_update(self,false)) return;
+
+	if ( self->bot_info->pickup_target==NULL ||
+		(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) || 
+		self->bot_info->pickup_target->solid==SOLID_NOT ||
+		!cr_moveto( self ) || is_touched(self, self->bot_info->move_target) ||
+		cr_no_way( self, self->bot_info->move_target ) ) {
+		// can't get there, let's return to our old target
+		cr_skip_pickup_target(self);
+		if (self->bot_info->old_think) self->think = self->bot_info->old_think;
+		else self->think = cr_think;
+		self->bot_info->old_think = NULL;
+	}
+
+	if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
+	else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
+}
+
+void cr_change_weapon( edict_t *self )
+{
+	int i;
+
+	self->client->pers.weapon = self->client->newweapon;
+	self->client->newweapon = NULL;
+	self->client->machinegun_shots = 0;
+
+	// set visible model
+	if (self->s.modelindex == 255) {
+		if (self->client->pers.weapon)
+			i = ((self->client->pers.weapon->weapmodel & 0xff) << 8);
+		else
+			i = 0;
+		self->s.skinnum = (self - g_edicts - 1) | i;
+	}
+
+	// gi.bprintf ( PRINT_MEDIUM, "new weapon selected %s\n", self->client->pers.weapon->classname );
+	self->bot_info->time_next_shot = level.time + TIME_WEAPON_CHANGE;
+
+	if (self->client->pers.weapon && self->client->pers.weapon->ammo)
+		self->client->ammo_index = ITEM_INDEX(FindItem(self->client->pers.weapon->ammo));
+	else
+		self->client->ammo_index = 0;
+}
+
+void cr_choose_best_weapon( edict_t *self )
+{
+	float enemy_dist = -1;
+
+	if (self->enemy) enemy_dist = dist( self->s.origin, self->enemy->s.origin );
+	else if (self->oldenemy) enemy_dist = dist( self->s.origin, self->oldenemy->s.origin );
+
+	if ( self->client->pers.inventory[INDEX_CELLS]>=50 &&  
+		self->client->pers.inventory[INDEX_BFG] ) {
+		self->client->newweapon = FindItem ("BFG10K");
+		return;
+	}
+	if ( self->client->pers.inventory[INDEX_SLUGS] &&  
+		self->client->pers.inventory[INDEX_RAILGUN] ) {
+		self->client->newweapon = FindItem ("railgun");
+		return;
+	}
+	if ( self->client->pers.inventory[INDEX_CELLS] &&  
+		self->client->pers.inventory[INDEX_HYPERBLASTER] ) {
+		self->client->newweapon = FindItem ("hyperblaster");
+		return;
+	}
+	if ( (enemy_dist<0 || enemy_dist>(120-self->health)) &&
+		self->client->pers.inventory[INDEX_ROCKETS] &&  
+		self->client->pers.inventory[INDEX_ROCKET_LAUNCHER] ) {
+		self->client->newweapon = FindItem ("rocket launcher");
+		return;
+	}
+	if ( (enemy_dist<0 || (enemy_dist>(120-self->health) && enemy_dist<500)) &&
+		self->client->pers.inventory[INDEX_GRENADES] &&  
+		self->client->pers.inventory[INDEX_GRENADE_LAUNCHER] ) {
+		self->client->newweapon = FindItem ("grenade launcher");
+		return;
+	}
+	if ( (enemy_dist<0 || enemy_dist<1000) &&
+		self->client->pers.inventory[INDEX_BULLETS] &&  
+		self->client->pers.inventory[INDEX_CHAINGUN] ) {
+		self->client->newweapon = FindItem ("chaingun");
+		return;
+	}
+	if ( self->client->pers.inventory[INDEX_BULLETS] &&
+		self->client->pers.inventory[INDEX_MACHINEGUN] ) {
+		self->client->newweapon = FindItem ("machinegun");
+		return;
+	}
+	if ( (enemy_dist<0 || enemy_dist<600) &&
+		self->client->pers.inventory[INDEX_SHELLS] &&  
+		self->client->pers.inventory[INDEX_SUPER_SHOTGUN] ) {
+		self->client->newweapon = FindItem ("super shotgun");
+		return;
+	}
+	if ( (enemy_dist<0 || enemy_dist<800) &&
+		self->client->pers.inventory[INDEX_SHELLS] &&  
+		self->client->pers.inventory[INDEX_SHOTGUN] ) {
+		self->client->newweapon = FindItem ("shotgun");
+		return;
+	}
+	self->client->newweapon = FindItem ("blaster");
+}
+
+static vec3_t  flash_offset = {
+	1.48, 7.4, 9.6 };
+
+qboolean cr_aim_ahead ( edict_t *self, edict_t *target, float aim_coeff, float speed,
+vec3_t start, vec3_t aim, qboolean aim_at_feet )
+{
+	vec3_t  end, forward, right, up, to;
+	float   tt, d;
+	int     tries;
+	trace_t trace;
+	float   skill_mod;
+	vec3_t  dir;
+	float   r, u, dd;
+
+	AngleVectors ( self->s.angles, forward, right, NULL );
+	G_ProjectSource ( self->s.origin, flash_offset, forward, right, start );
+
+	if (!target) {
+		VectorSubtract( self->bot_info->shoot_last_target, start, aim );
+		VectorNormalize( aim );
+		return true;
+	}
+
+	skill_mod = (11-self->bot_pers->skill)*0.1f;
+
+	VectorCopy( target->s.origin, to );
+	if (aim_at_feet) to[2] += target->mins[2]*(0.5f+0.4f*qrandom())*(1.f-skill_mod);
+
+	VectorSubtract( to, start, aim );
+	d = VectorLength(aim);
+	VectorCopy( to, self->bot_info->shoot_last_target );
+
+	for ( tries=0; ; tries++) {
+
+		tt = 0.8f*aim_coeff*crandom()*skill_mod;
+		if (speed>0) tt += d/speed;
+		VectorMA ( to, tt, target->velocity, end );
+
+		VectorSubtract ( end, start, aim );
+
+		vectoangles ( aim, dir );
+		AngleVectors ( dir, forward, right, up );
+
+		dd = d*skill_mod*(0.05f+qrandom());
+		r = dd*crandom()*BOT_ACCURACY_HORZ*aim_coeff;
+		u = dd*crandom()*BOT_ACCURACY_VERT*aim_coeff;
+		VectorMA ( aim, r, right, aim );
+		VectorMA ( aim, u, up, aim );
+
+		VectorNormalize(aim);
+
+		// test if we can get to the target shooting like this
+		VectorMA( start, d, aim, end );
+		trace = gi.trace ( start, vec3_origin, vec3_origin, end, self, MASK_SHOT );
+
+		if (trace.startsolid || trace.allsolid) return false;
+		if (trace.fraction>0.9f || trace.ent==target) break;
+		if (tries>2 && self->client->pers.team_no>0 && trace.ent->client &&
+			self->client->pers.team_no!=trace.ent->client->pers.team_no) break;
+		if (tries>4) return false;
+	}
+
+	VectorMA( start, d, aim, self->bot_info->shoot_last_target );
+
+	return true;
+}
+
+qboolean cr_fire_weapon( edict_t *self, edict_t *target )
+{
+	vec3_t   start, aim;
+	int      damage, damage_amp, cur_weapon;
+	qboolean b_quad;
+
+	self->bot_info->b_shot_this_frame = true;
+
+	if (self->bot_info->time_next_weapon_change>self->bot_info->time_stop_shoting)
+		self->bot_info->time_next_weapon_change = self->bot_info->time_stop_shoting;
+
+	if (self->bot_info->time_next_weapon_change < level.time) {
+		cr_choose_best_weapon(self);
+		if ( self->client->pers.weapon != self->client->newweapon ) {
+			self->bot_info->time_next_weapon_change = level.time + 1.5f*TIME_WEAPON_CHANGE;
+			cr_change_weapon(self);
+			return false;
+		}
+	}
+
+	cur_weapon = ITEM_INDEX(self->client->pers.weapon);
+
+	if (target) {
+		if (cur_weapon == INDEX_RAILGUN) {
+			self->bot_info->time_stop_shoting = level.time + TIME_RAILGUN_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_HYPERBLASTER ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_HYPERBLASTER_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_ROCKET_LAUNCHER ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_ROCKET_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_GRENADE_LAUNCHER ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_GRENADE_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_CHAINGUN ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_CHAINGUN_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_MACHINEGUN ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_MACHINEGUN_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_SUPER_SHOTGUN ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_SUPERSHOTGUN_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_SHOTGUN ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_SHOTGUN_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_BLASTER ) {
+			self->bot_info->time_stop_shoting = level.time + TIME_BLASTER_AFTERSHOT;
+		}
+		else if ( cur_weapon==INDEX_BFG ) {
+			if ( self->bot_info->time_next_shot <= level.time &&
+				self->bot_info->time_weapon_spin_down <= level.time &&
+				self->bot_info->time_weapon_spin_up < level.time ) {
+				cr_effect(self,MZ_BFG);
+				self->bot_info->time_stop_shoting = level.time + TIME_BFG_AFTERSHOT;
+				self->bot_info->time_weapon_spin_up = level.time + TIME_BFG_SPINUP;
+				return true;
+			}
+		}
+		else {
+			self->bot_info->time_stop_shoting = level.time + FRAMETIME/2;
+		}
+	}
+	else {
+		if ( cur_weapon==INDEX_HYPERBLASTER ) {
+			self->bot_info->time_weapon_spin_down = level.time + TIME_WEAPON_CHANGE;
+		}
+		else if ( cur_weapon==INDEX_CHAINGUN ) {
+			self->bot_info->time_weapon_spin_down = level.time + TIME_WEAPON_CHANGE;
+		}
+		else {
+			self->bot_info->time_weapon_spin_down = level.time - FRAMETIME;
+		}
+	}
+
+	if ( self->bot_info->time_next_shot > level.time ) return false;
+	if ( self->bot_info->time_weapon_spin_down > level.time ) return false;
+	if ( self->bot_info->time_weapon_spin_up > level.time ) return false;
+
+	if ( (int)dmflags->value & DF_INFINITE_AMMO ) {
+		if (self->client->ammo_index) self->client->pers.inventory[self->client->ammo_index] = 999;
+	}
+
+	if (self->bot_info->time_next_fight_message<level.time) {
+		cr_message_fight ( self, target );
+		self->bot_info->time_next_fight_message = level.time + FIGHT_MSG_DELAY*(0.7f+0.6f*qrandom());
+	}
+
+	b_quad = self->client->quad_framenum > level.framenum;
+	damage_amp = 1;
+	if ( b_quad ) damage_amp = 4;
+	if ( self->client->pers.inventory[INDEX_TECH2] ) damage_amp *= 2;
+
+#define hasted(x) (CTFApplyHaste(self) ? (x)/2 : (x))
+
+	if ( cur_weapon==INDEX_RAILGUN ) {
+		// fire "railgun"
+		if (!cr_aim_ahead( self, target, 2.f, RAILGUN_SPEED, start, aim, false )) return false;
+		damage = RAILGUN_DAMAGE*damage_amp;
+		fire_rail ( self, start, aim, damage, b_quad ? 800 : 200 );
+		cr_effect( self, MZ_RAILGUN );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_RAILGUN_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_HYPERBLASTER ) {
+		// fire "hyperblaster"
+		if (!cr_aim_ahead( self, target, 1.6f, HYPERBLASTER_SPEED, start, aim, false )) return false;
+		damage = HYPERBLASTER_DAMAGE*damage_amp;
+		fire_blaster ( self, start, aim, damage, BLASTER_SPEED, EF_HYPERBLASTER, true );
+		cr_effect( self, MZ_HYPERBLASTER );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_HYPERBLASTER_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_BFG ) {
+		// fire "BFG"
+		if (!cr_aim_ahead( self, target, 1.f, BFG_SPEED, start, aim, false )) return false;
+		damage = BFG_DAMAGE*damage_amp;
+		fire_bfg ( self, start, aim, damage, BFG_SPEED, 1000 );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_BFG_SHOT);
+		self->bot_info->time_weapon_spin_down = level.time + TIME_BFG_SPINUP;
+		self->client->pers.inventory[self->client->ammo_index] -= 50;
+		return true;
+	}
+	if ( cur_weapon==INDEX_ROCKET_LAUNCHER ) {
+		// fire "hyperblaster"
+		if (!cr_aim_ahead( self, target, 0.6f, ROCKET_SPEED, start, aim, true )) return false;
+		VectorMA( start, 5.f, aim, start );
+		damage = (1.f+0.2f*qrandom())*ROCKET_DAMAGE*damage_amp;
+		fire_rocket ( self, start, aim, damage, ROCKET_SPEED, 120/*damage_rad*/, damage/*radius_damage*/ );
+		cr_effect( self, MZ_ROCKET );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_ROCKET_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_GRENADE_LAUNCHER ) {
+		// fire "grenade"
+		if (!cr_aim_ahead( self, target, 0.4f, GRENADE_SPEED, start, aim, rand()%1 )) return false;
+		damage = GRENADE_DAMAGE*damage_amp;
+		fire_grenade ( self, start, aim, damage, GRENADE_SPEED, 2.5, damage+40 );
+		cr_effect( self, MZ_GRENADE );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_GRENADE_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_CHAINGUN ) {
+		// fire "chaingun"
+		if (!cr_aim_ahead( self, target, 1.8f, CHAINGUN_SPEED, start, aim, false )) return false;
+		damage = CHAINGUN_DAMAGE*damage_amp;
+		fire_bullet ( self, start, aim, damage, b_quad ? 8 : 2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN );
+		cr_effect( self, MZ_CHAINGUN2 );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_CHAINGUN_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_MACHINEGUN ) {
+		// fire "machinegun"
+		if (!cr_aim_ahead( self, target, 1.1f, MACHINEGUN_SPEED, start, aim, false )) return false;
+		damage = MACHINEGUN_DAMAGE*damage_amp;
+		fire_bullet ( self, start, aim, damage, b_quad ? 8 : 2, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN );
+		cr_effect( self, MZ_MACHINEGUN );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_MACHINEGUN_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_SUPER_SHOTGUN ) {
+		// fire "super shotgun"
+		if (!cr_aim_ahead( self, target, 1.4f, SUPERSHOTGUN_SPEED, start, aim, false )) return false;
+		damage = SUPERSHOTGUN_DAMAGE*damage_amp;
+		fire_shotgun ( self, start, aim, damage, b_quad ? 4*12 : 12, 
+			DEFAULT_SHOTGUN_HSPREAD, 1.6f*DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT, MOD_SSHOTGUN );
+		cr_effect( self, MZ_SSHOTGUN );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_SUPERSHOTGUN_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_SHOTGUN ) {
+		// fire "shotgun"
+		if (!cr_aim_ahead( self, target, 1.f, SHOTGUN_SPEED, start, aim, false )) return false;
+		damage = SHOTGUN_DAMAGE*damage_amp;
+		fire_shotgun ( self, start, aim, damage, b_quad ? 4*8 : 8, 
+			500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN );
+		cr_effect( self, MZ_SHOTGUN );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_SHOTGUN_SHOT);
+		self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+		return true;
+	}
+	if ( cur_weapon==INDEX_BLASTER ) {
+		// fire "blaster"
+		if (!cr_aim_ahead( self, target, 0.8f, BLASTER_SPEED, start, aim, false )) return false;
+		damage = BLASTER_DAMAGE*damage_amp;
+		fire_blaster ( self, start, aim, damage, BLASTER_SPEED, EF_BLASTER, false );
+		cr_effect( self, MZ_BLASTER );
+		self->bot_info->time_next_shot = level.time + hasted(TIME_BLASTER_SHOT);
+		return true;
+	}
+
+#undef hasted
+
+	return false;
+}
+
+qboolean cr_find_route( edict_t *self, vec3_t target, qboolean b_important )
+{
+	path_node_t *node_stack1[MAX_STACK_NODE], *node_stack2[MAX_STACK_NODE];
+	path_node_t *node, *link_node, *target_node, *next_link;
+	path_node_t **nodes, **new_nodes;
+	int          cur_stack, stack_nodes, new_stack_nodes, i, j, max_nodes, n_count;
+	float        cur_dist, best_dist, d;
+
+	self->bot_info->next_node=NULL;
+	self->bot_info->last_node = cr_find_closest_node(self);
+
+	VectorCopy ( self->s.origin, self->s.old_origin);
+	VectorCopy ( target, self->s.origin );
+	target_node = cr_find_closest_node(self);
+	VectorCopy ( self->s.old_origin, self->s.origin );
+
+	self->bot_info->target_node = target_node;
+
+	if (target_node == self->bot_info->last_node) {
+		// gi.bprintf ( PRINT_MEDIUM, "Can't find route: starting node == target node\n" );
+		return false;
+	}
+
+	// initialize node network
+	for ( node=cr_node_head; node!=NULL; node=node->next ) node->route_dist=-1;
+
+	max_nodes = 20 + 3*self->bot_pers->skill;
+	if (b_important) max_nodes *= 2;
+	if (max_nodes>(MAX_PATH_NODES-2)) max_nodes = MAX_PATH_NODES-2;
+
+	// find actual route
+	self->bot_info->last_node->route_dist = 0.001;
+	node_stack1[0] = self->bot_info->last_node;
+	new_stack_nodes = 1;
+	cur_stack = 0;
+
+	n_count=0;
+	while (n_count<max_nodes) {
+		stack_nodes = new_stack_nodes;
+		new_stack_nodes = 0;
+
+		if (cur_stack==0) {
+			nodes     = node_stack1;
+			new_nodes = node_stack2;
+			cur_stack=1;
+		}
+		else {
+			nodes     = node_stack2;
+			new_nodes = node_stack1;
+			cur_stack=0;
+		}
+
+		best_dist = 1e32;
+		for ( i=0; i<stack_nodes; i++ ) {
+			node = *nodes++;
+			cur_dist = node->route_dist;
+			if (best_dist>cur_dist) best_dist=cur_dist;
+
+			// go through all links
+			for ( j=0; j<MAX_NODE_LINKS; j++ ) {
+				if ( !(link_node = node->link_to[j]) ) break;
+				d = cur_dist + node->link_dist[j];
+
+				if (link_node!=target_node && link_node->route_dist<0 && new_stack_nodes<MAX_STACK_NODE) {
+					// add new downlink node
+					*new_nodes = link_node;
+					new_nodes++;
+					new_stack_nodes++;
+				}
+
+				if ( link_node->route_dist<0 || link_node->route_dist>d ) {
+					// update route_dist if this route is shorter
+					link_node->route_dist = d;
+				}
+			}
+		}
+
+		if (!new_stack_nodes) {
+			// gi.bprintf ( PRINT_MEDIUM, "no new nodes! \n" );
+			break;
+		}
+		if ( target_node->route_dist>=0 && target_node->route_dist<best_dist ) {
+			// gi.bprintf ( PRINT_MEDIUM, "Route found! %.2f\n", target_node->route_dist );
+			break;
+		}
+
+		n_count++;
+	}
+
+	if ( target_node->route_dist<0 ) {
+		//gi.bprintf ( PRINT_MEDIUM, "Can't find route: no route to target\n" );
+		return false;
+	}
+
+	/*
+   // remove path indication
+    { edict_t* line, *next_line;
+    for ( line = global_path_route; line!=NULL; line=next_line ) {
+       next_line = line->teamchain;
+       G_FreeEdict(line);
+       }
+    } */
+
+	self->bot_info->path_nodes=-1;
+	n_count=0;
+	node = target_node;
+	while (n_count<=max_nodes) {
+		// search for shortest possible distance from "node"
+		best_dist=0;
+		link_node=NULL;
+		for ( i=0; i<MAX_NODE_LINKS; i++ ) {
+			if ( !(next_link = node->link_from[i]) ) break;
+			d = next_link->route_dist;
+			if (d>=0 && (!link_node || best_dist>d) ) {
+				link_node = next_link;
+				best_dist = d;
+			}
+		}
+
+		self->bot_info->path[++self->bot_info->path_nodes]=node;
+
+		if (!link_node || link_node==self->bot_info->last_node) break;
+
+		node->route_dist = -2;
+
+		if(_DEBUG)
+			cr_draw_line( node->position, link_node->position, COLOR_BEAM_GREEN, &global_path_route );
+		node = link_node;
+		n_count++;
+	}
+
+	self->bot_info->next_node = node;
+	cr_set_move_target( self, node->position );
+
+	return true;
+}
+
+void cr_try_to_find_route(  edict_t *self, vec3_t target )
+{
+	if (!cr_find_route( self, target, false )) {
+		cr_skip_enemy(self);
+		self->think = cr_think;
+	}
+	else {
+		self->think = cr_think_chase_route;
+	}
+}
+
+/*
+qboolean cr_try_to_hide( edict_t *self )
+{
+    if ( (level.time - self->bot_info->time_indarkness) > FRAMETIME*5 ) {
+      gi.dprintf( "%s is hiding at %.1f %.1f %.1f!\n", self->client->pers.netname, self->s.origin[0], self->s.origin[1], self->s.origin[2] );
+      cr_crouch(self,true);
+      self->think = cr_think_hide;
+      return true;
+      } 
+    return false;
+}
+
+void cr_think_hide( edict_t *self )
+{
+    if (!cr_update(self,true)) return;
+
+   cr_animate_frames( self, FRAME_crstnd01, FRAME_crstnd19 );
+}
+*/
+
+void cr_think_run_for_life ( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+	//FIXME: implement searching for health only!
+
+	if (!self->bot_info->pickup_target) {
+		self->think = cr_think;
+		return;
+	}
+
+	bStuck = !cr_moveto(self);
+
+	if ( is_touched( self, self->bot_info->move_target ) ) {
+		if ( self->bot_info->pickup_target &&
+			is_touched( self, self->bot_info->pickup_target->s.origin ) ) {
+			self->bot_info->pickup_target=NULL;
+		}
+		else {
+			self->bot_info->last_node = self->bot_info->next_node;
+			self->bot_info->next_node = cr_get_next_path_node(self);
+			if (self->bot_info->next_node)
+				cr_set_move_target( self, self->bot_info->next_node->position );
+			else {
+				self->bot_info->pickup_target=NULL;
+				cr_find_pickup_on_map(self);
+			}
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			cr_skip_pickup_target(self);
+		}
+
+	if (self->bot_info->pickup_target) {
+		if ( !self->bot_info->pickup_target->inuse ||
+			(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) || 
+			self->bot_info->pickup_target->solid==SOLID_NOT ) {
+			cr_forget_pickup_target(self);
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_chase_route( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+
+	if (cr_try_pickup_urgent(self)) return;
+
+	cr_find_enemy(self);
+	if (!self->enemy) {
+		if ( level.time > self->bot_info->time_chase ) {
+			self->bot_info->next_node=NULL;
+			self->think = cr_think;
+			return;
+		}
+		if ( self->oldenemy && 
+			(level.time-self->monsterinfo.trail_time)>(5.f*qrandom()*(11-self->bot_pers->skill)) ) {
+			VectorCopy( self->oldenemy->s.origin, self->monsterinfo.last_sighting );
+			self->monsterinfo.trail_time = level.time;
+			cr_try_to_find_route( self, self->monsterinfo.last_sighting );
+		}
+	}
+	else {
+		self->bot_info->time_chase = level.time + CHASE_TIME*(0.9f+qrandom()*0.2f);
+	}
+
+	//FIXME: check for new position not so often
+	if ( self->enemy &&
+		cr_find_closest_enemy_node(self)!=self->bot_info->target_node ) {
+		cr_try_to_find_route( self, self->enemy->s.origin );
+	}
+
+	//FIXME: dodge while following the path
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			self->think = cr_think;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			// let's find detour!
+			if (self->enemy) cr_try_to_find_route( self, self->enemy->s.origin );
+			else cr_try_to_find_route( self, self->monsterinfo.last_sighting );
+		}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_follow_route( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+
+	bStuck = !cr_moveto(self);
+
+	// move towards enemy by following the route
+	if (is_touched( self, self->bot_info->move_target )) {
+		// got there!
+		self->bot_info->last_node = self->bot_info->next_node;
+		self->bot_info->next_node = cr_get_next_path_node(self);
+		if (self->bot_info->next_node)
+			cr_set_move_target( self, self->bot_info->next_node->position );
+		else {
+			self->think = cr_think;
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there 
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			if (!cr_find_route( self, self->monsterinfo.last_sighting, false )) {
+				self->think = cr_think;
+			}
+		}
+
+	if ( (level.time-self->monsterinfo.trail_time)>TEAM_HELP_TIME ) {
+		self->think = cr_think;
+	}
+
+	cr_fire_and_run(self);
+}
+
+void cr_think_chase (edict_t *self)
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+
+	if (cr_try_pickup_urgent(self)) return;
+
+	cr_find_enemy(self);
+	// move towards enemy
+	if (!self->enemy) {
+		if ( level.time > self->bot_info->time_chase ) {
+			self->think = cr_think;
+			return;
+		}
+		cr_set_move_target( self, self->monsterinfo.last_sighting );
+	}
+	else {
+		self->bot_info->time_chase = level.time + CHASE_TIME*(0.9f+qrandom()*0.2f);
+		cr_set_move_target( self, self->enemy->s.origin );
+	}
+
+	//FIXME: dodge while going towards the enemy
+	bStuck = !cr_moveto(self);
+
+	if (is_touched( self, self->bot_info->move_target )) {
+		if ( self->oldenemy && (10*qrandom())<self->bot_pers->skill &&
+			ITEM_INDEX(self->client->pers.weapon)>INDEX_SUPER_SHOTGUN ) {
+			VectorCopy( self->oldenemy->s.origin, self->monsterinfo.last_sighting );
+			self->monsterinfo.trail_time = level.time;
+			cr_try_to_find_route( self, self->monsterinfo.last_sighting );
+		}
+		else
+			self->think = cr_think;
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			// can't get there, let's find detour!
+			cr_try_to_find_route( self, self->bot_info->move_target );
+		}
+
+	cr_fire_and_run(self);
+}
+
+void cr_switch_strafe( edict_t *self )
+{
+	self->bot_info->time_last_strafe_switch = level.time + 0.1f;
+	self->bot_info->strafe_dir = -self->bot_info->strafe_dir;
+}
+
+void cr_think_attack( edict_t *self )
+{
+	float    d, strafe_a;
+	vec3_t   move;
+	qboolean bSwitchStrafeDir;
+
+	if (!cr_update(self,true)) return;
+
+	if (!self->enemy && self->oldenemy) {
+		if (self->oldenemy->deadflag==DEAD_DEAD) {
+			if (self->oldenemy->client && !self->oldenemy->bot_info) self->bot_pers->adapt_count--;
+			if (qrandom()<0.3f) cr_message_kill( self, self->oldenemy );
+			if ( (int)bot_taunt->value && !cr_find_enemy(self) && 
+				!self->bot_info->b_crouch && !self->waterlevel )
+				self->think = qrandom()<0.3f ? cr_think_taunt : cr_think_flip;
+			self->oldenemy=NULL;
+			return;
+		}
+	}
+
+	if (cr_try_pickup_urgent(self)) return;
+
+	d = distance( self, self->enemy );
+	if (!self->enemy || d>self->bot_pers->attack_range*1.1f) {
+		cr_switch_strafe(self);
+		self->think = cr_think_chase;
+		return;
+	}
+
+	if (self->bot_info->strafe_dir>0) self->bot_info->strafe_dir=1;
+	else self->bot_info->strafe_dir=-1;
+
+	if ( 50*qrandom()<(self->bot_pers->attack_range - d) ) strafe_a = 98 + 8*qrandom();
+	else strafe_a = 82 - 8*qrandom();
+
+	// face the enemy
+	VectorSubtract ( self->enemy->s.origin, self->s.origin, move );
+	self->ideal_yaw = vectoyaw(move);
+	M_ChangeYaw(self);
+
+	self->ideal_yaw = anglemod( self->ideal_yaw + self->bot_info->strafe_dir*strafe_a );
+
+	bSwitchStrafeDir = (3+1.6f*(10-self->bot_pers->skill))*qrandom()<(level.time-self->bot_info->time_last_strafe_switch);
+
+	if (!cr_move_yaw( self, false, true )) bSwitchStrafeDir=true;
+
+	if (bSwitchStrafeDir) cr_switch_strafe(self);
+
+	cr_fire_and_run(self);
+}
+
+void cr_sight(edict_t *self, edict_t *)
+{
+	self->think = cr_think_chase;
+}
+
+void cr_think_taunt ( edict_t *self )
+{
+	if (!cr_update(self,false)) return;
+
+	self->bot_info->time_last_stuck=0;
+	self->bot_info->stuck_count=0;
+
+	cr_run_frames( self, FRAME_taunt01, FRAME_taunt17 );
+
+	if (self->s.frame==FRAME_taunt17) {
+		self->think = cr_think;
+	}
+}
+
+void cr_think_flip ( edict_t *self )
+{
+	if (!cr_update(self,false)) return;
+
+	self->bot_info->time_last_stuck=0;
+	self->bot_info->stuck_count=0;
+
+	cr_run_frames( self, FRAME_flip01, FRAME_flip12 );
+
+	if (self->s.frame==FRAME_flip12) {
+		self->think = cr_think;
+	}
+}
+
+void cr_think_salute ( edict_t *self )
+{
+	if (!cr_update(self,false)) return;
+
+	self->bot_info->time_last_stuck=0;
+	self->bot_info->stuck_count=0;
+
+	cr_run_frames( self, FRAME_salute01, FRAME_salute11 );
+
+	if ( level.time < self->bot_info->time_next_salute ||
+		self->s.frame==FRAME_salute11 ) {
+		self->bot_info->time_next_salute = level.time + 10.f+3*qrandom();
+		if (self->bot_info->bot_assignment==ASSN_GROUP)
+			self->think = cr_think_wait;
+		else
+			if (self->bot_info->bot_assignment==ASSN_GUARD)
+				self->think = cr_think_wait;
+			else
+				if (self->bot_info->bot_assignment==ASSN_PATROL)
+					self->think = cr_think_team_patrol;
+				else 
+					self->think = cr_think;
+	}
+}
+
+void cr_think_pickup ( edict_t *self )
+{
+	qboolean bStuck;
+
+	if (!cr_update(self,true)) return;
+
+	if (!self->bot_info->pickup_target) {
+		self->bot_info->last_node=NULL;
+		self->bot_info->next_node=NULL;
+		self->think = cr_think;
+		return;
+	}
+
+	if (cr_find_enemy(self)) {
+		cr_sight(self,self->enemy);
+		return;
+	}
+
+	bStuck = !cr_moveto(self);
+
+	if (is_touched( self, self->bot_info->move_target )) {
+		// gi.bprintf ( PRINT_MEDIUM, "got to waypoint!\n" );
+		if ( self->bot_info->pickup_target &&
+			is_touched( self, self->bot_info->pickup_target->s.origin ) ) {
+			self->bot_info->pickup_target=NULL;
+		}
+		else {
+			self->bot_info->last_node = self->bot_info->next_node;
+			self->bot_info->next_node = cr_get_next_path_node(self);
+			if (self->bot_info->next_node)
+				cr_set_move_target( self, self->bot_info->next_node->position );
+			else {
+				self->bot_info->pickup_target=NULL;
+				// gi.bprintf ( PRINT_MEDIUM, "path ended!\n" );
+			}
+			//cr_find_pickup_on_map(self);
+		}
+	}
+	else
+		if ( bStuck || cr_no_way( self, self->bot_info->move_target ) ) {
+			cr_remove_direct_route( self->bot_info->last_node, self->bot_info->next_node );
+			cr_skip_pickup_target(self);
+			// gi.bprintf ( PRINT_MEDIUM, "can't get there!\n" );
+		}
+
+	if ( self->bot_info->pickup_target &&
+		// special case: missing flag on CTF base
+	!( (self->client->pers.inventory[INDEX_FLAG2]>0 && 
+		strcmp( self->bot_info->pickup_target->classname, "item_flag_team1")==0) ||
+		(self->client->pers.inventory[INDEX_FLAG1]>0 && 
+		strcmp( self->bot_info->pickup_target->classname, "item_flag_team2")==0) ) ) {
+		if (!self->bot_info->pickup_target->inuse ||
+			(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) || 
+			self->bot_info->pickup_target->solid==SOLID_NOT ) {
+			// gi.bprintf ( PRINT_MEDIUM, "somebody picked it up before me!\n" );
+			cr_forget_pickup_target(self);
+		}
+	}
+
+	cr_fire_and_run(self);
+}
+
+qboolean cr_return_to_base( edict_t *self )
+{
+	edict_t *hit;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse || !hit->item) continue;
+		// if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+		if (hit->spawnflags & DROPPED_ITEM) continue;
+
+		if (hit->item->pickup==CTFPickup_Flag) {
+			if ( self->client->resp.ctf_team==CTF_TEAM1 && 
+				strcmp(hit->classname, "item_flag_team1")==0 ) break;
+			if ( self->client->resp.ctf_team==CTF_TEAM2 && 
+				strcmp(hit->classname, "item_flag_team2")==0 ) break;
+		}
+	}
+
+	if (!hit) {
+		// gi.dprintf("NO BASE FLAG FOUND!\n"); //!!!
+		return false;
+	}
+
+	return cr_force_pickup_target( self, hit );
+}
+
+qboolean cr_try_special_assignment( edict_t *self )
+{
+	edict_t *hit, *enemy_flag, *team_flag;
+	int      flag;
+
+	if (!ctf->value) return false;
+
+	if ( self->bot_info->time_next_special_assignment > level.time ) return false;
+	self->bot_info->time_next_special_assignment = level.time + 8.f + 2*qrandom();
+
+	// special case: we have enemy flag, let's go return it
+	if ((self->client->pers.inventory[INDEX_FLAG1]>0) ||
+		(self->client->pers.inventory[INDEX_FLAG2]>0)) {
+		if (cr_return_to_base(self)) return true;
+	}
+
+	enemy_flag=NULL;
+	team_flag=NULL;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+		if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+
+		if (hit->item && hit->item->pickup==CTFPickup_Flag) {
+			if (strcmp(hit->classname, "item_flag_team1")==0) flag=CTF_TEAM1;
+			else if (strcmp(hit->classname, "item_flag_team2")==0) flag=CTF_TEAM2;
+			else continue;
+
+			if ( flag==self->client->resp.ctf_team ) team_flag=hit;
+			else enemy_flag=hit;
+		}
+		else 
+			if ( hit->client && 
+				(hit->client->pers.inventory[INDEX_FLAG1] || hit->client->pers.inventory[INDEX_FLAG2]) ) {
+				if ( hit->client->resp.ctf_team!=self->client->resp.ctf_team ) team_flag=hit;
+				else enemy_flag=hit;
+			}
+
+		if (enemy_flag && team_flag) break;
+	}
+
+	if (team_flag) {
+		// check if team_flag at base and thus alright
+		if (!team_flag->client && !(team_flag->spawnflags & DROPPED_ITEM)) {
+			// if we have enemy flag, let's go score a capture!
+			if (self->client->pers.inventory[INDEX_FLAG1] || self->client->pers.inventory[INDEX_FLAG2]) {
+				if (team_flag && cr_force_pickup_target( self, team_flag )) {
+					// gi.dprintf ( "%s going to finish the capture!\n", self->client->pers.netname ); 
+					return true;
+				}
+				//  else {
+				//    gi.dprintf ( "%s doesn't know going to get enemy flag to the base!\n", self->client->pers.netname ); 
+				//    }
+			}
+		}
+		else {
+			// rescue our team_flag! 
+			if (team_flag->client) {
+				if ( (rand()%1) && cr_force_attack_enemy( self, team_flag )) {
+					// gi.dprintf ( "%s going to kill flag carrier!\n", self->client->pers.netname ); 
+					return true;
+				}
+			}
+			else {
+				if (cr_force_pickup_target( self, team_flag )) {
+					// gi.dprintf ( "%s going to return the flag!\n", self->client->pers.netname ); 
+					return true;
+				}
+			}
+		}
+	}
+
+	if (enemy_flag && !enemy_flag->client) {
+		if (cr_force_pickup_target( self, enemy_flag )) {
+			// gi.dprintf ( "%s going to capture the flag!\n", self->client->pers.netname ); 
+			return true;
+		}
+	}
+
+	return false;
+}
+
+void cr_think ( edict_t *self )
+{
+	if (!cr_update(self,true)) return;
+
+	if (level.time > self->bot_info->time_next_assignment_check) {
+		self->bot_info->time_next_assignment_check = level.time+3.f;
+		if (self->bot_info->bot_assignment==ASSN_GROUP) {
+			self->think = cr_think_team_group;
+			return;
+		}
+		else
+			if (self->bot_info->bot_assignment==ASSN_GUARD) {
+				self->think = cr_think_team_guard;
+				return;
+			}
+			else 
+				if (self->bot_info->bot_assignment!=ASSN_NONE) {
+					cr_dismiss(self);
+				}
+	}
+
+	if (cr_find_enemy(self)) {
+		cr_sight(self,self->enemy);
+		return;
+	}
+	if (cr_try_special_assignment(self)) return;
+
+	cr_find_pickup_target(self);
+	if (!self->bot_info->pickup_target && 
+		self->bot_info->bot_assignment==ASSN_NONE) {
+		// let's go for pickups we saw once
+		self->bot_info->pickup_target_score = 1e32;
+		cr_find_pickup_on_map(self);
+	}
+
+	if (!self->bot_info->pickup_target) {
+		if ( level.time > self->bot_info->time_next_roam_dir_change ||
+			!cr_move_yaw( self, true, qrandom()>0.02f ) ) {
+			float    dy;
+			qboolean bReverse;
+
+			self->ideal_yaw = anglemod( self->ideal_yaw + 10*(0.5f-qrandom()) );
+
+			bReverse = qrandom()>0.5f;
+			for ( dy=60; ; dy+=30*(0.8f+0.2f*qrandom()) ) {
+				if (bReverse) dy = -dy;
+				if (cr_try_move(self,  dy, true )) break;
+				if (cr_try_move(self, -dy, true )) break;
+				if (bReverse) dy = -dy;
+				if (dy>120) {
+					self->ideal_yaw = 360*qrandom();
+					break;
+				}
+			}
+			self->bot_info->time_next_roam_dir_change = level.time + TIME_ROAM_DIR_CHANGE;
+		}
+	}
+	else
+		if ( !cr_moveto(self) ||
+			is_touched2d( self, self->bot_info->move_target ) ) {
+			cr_skip_pickup_target(self);
+			cr_pick_random_destination(self);
+		}
+
+	if (self->bot_info->pickup_target) {
+		if ( !self->bot_info->pickup_target->inuse ||
+			(self->bot_info->pickup_target->svflags & SVF_NOCLIENT) || 
+			self->bot_info->pickup_target->solid==SOLID_NOT )
+			cr_forget_pickup_target(self);
+	}
+
+	if (!self->bot_info->b_crouch) cr_animate_frames( self, FRAME_run1, FRAME_run6 );
+	else cr_animate_frames( self, FRAME_crwalk1, FRAME_crwalk6 );
+}
+
+void cr_run_frames( edict_t *self, int start, int end )
+{
+	if ( self->s.frame<start || 
+		self->s.frame>=end )
+		self->s.frame = start;
+	else
+		self->s.frame++;
+}
+
+void cr_animate_frames( edict_t *self, int start, int end )
+{
+	if (self->client->anim_priority==ANIM_DEATH) {
+		self->s.frame = self->client->anim_end;
+		self->think = cr_death;
+		return;
+	}
+
+	if (!self->groundentity && !self->waterlevel) {
+		if (self->s.frame<FRAME_jump1 || self->s.frame>FRAME_jump6) self->s.frame = FRAME_jump1;
+		if (self->s.frame!=FRAME_jump6) self->s.frame++;
+		return;
+	}
+
+	cr_run_frames(self,start,end);
+
+	if (!self->waterlevel) {
+		switch (self->s.frame) {
+		case FRAME_crwalk3:
+			gi.sound ( self, CHAN_BODY, sound_footstep[(int)(qrandom()*4)], 0.4f, ATTN_NORM, 0 );
+			break;
+		case FRAME_run2:
+		case FRAME_run5:
+			gi.sound ( self, CHAN_BODY, sound_footstep[(int)(qrandom()*4)], 0.6f, ATTN_NORM, 0 );
+			break;
+		}
+	}
+}
+
+void cr_routes_load(void);
+
+void cr_init_node_net(void)
+{
+	int         count=0;
+	edict_t     *hit, *other;
+	path_node_t *node, *node2;
+	vec3_t       pos;
+
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+
+		if (hit->touch==teleporter_touch) {
+			other = G_Find( NULL, FOFS(targetname), hit->target );
+			if (!other) continue;
+			node = cr_insert_node( hit->s.origin, NULL, 0 );
+			node2 = cr_insert_node( other->s.origin, NULL, NF_TELEPORT );
+			cr_add_direct_route( node, node2, false );
+			continue;
+		}
+		if (hit->touch == Touch_Plat_Center) {
+			pos[0] = (hit->absmin[0]+hit->absmax[0])/2;
+			pos[1] = (hit->absmin[1]+hit->absmax[1])/2;
+			pos[2] = hit->absmin[2] + STEPSIZE;
+			node = cr_insert_node( pos, NULL, NF_ELEVATOR );
+			pos[2] = hit->absmax[2] + STEPSIZE;
+			node2 = cr_insert_node( pos, NULL, NF_ELEVATOR );
+			cr_add_direct_route_uni( node, node2 );
+			continue;
+		}
+		if (hit->use == door_use) {
+			pos[0] = (hit->absmax[0]+hit->absmin[0])/2;
+			pos[1] = (hit->absmax[1]+hit->absmin[1])/2;
+			pos[2] = (hit->absmax[2]+hit->absmin[2]*3)/4;
+			continue;
+		}
+
+		if (!hit->item || !hit->item->pickup) continue;
+
+		// insert new node
+		node = cr_insert_node( hit->s.origin, NULL, 0 );
+		node->item = hit;
+
+		count++;
+	}
+
+	cr_routes_load();
+}
+
+void cr_check_pending_bots(void)
+{
+	int      i, pn;
+	edict_t* new_bot;
+
+	for ( i=0; i<global_bots_count; i++ ) {
+		if (!global_bots[i].b_inuse || global_bots[i].playernum>=0) continue;
+		pn = cr_find_unused_client_slot();
+		if (pn<0) break;
+		global_bots[i].playernum = pn;
+		new_bot = cr_respawn_bot( global_bots+i, NULL );
+		if (new_bot) {
+			new_bot->classname = "new_bot";
+			cr_update_userinfo(new_bot->bot_pers);
+			new_bot->client->resp.ctf_team = CTF_NOTEAM;
+		}
+	}
+}
+
+void
+cr_debug_draw(edict_t* p)
+{
+	edict_t *hit;
+	edict_t *line, *d;
+
+	//!!! remove path indication
+	for(line = global_path_route; line != nil; line = d){
+		d = line->teamchain;
+		G_FreeEdict(line);
+	}
+
+	for(hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++){
+		if(!hit->inuse)
+			continue;
+		if(hit->svflags & SVF_NOCLIENT || hit->solid == SOLID_NOT)
+			continue;
+
+		if(hit->item && hit->item->pickup == CTFPickup_Flag){
+			p->bot_info = Z_TagMalloc(sizeof *p->bot_info, TAG_GAME);
+			memset(p->bot_info, 0, sizeof *p->bot_info);
+			p->bot_pers = Z_TagMalloc(sizeof *p->bot_pers, TAG_GAME);
+			memset(p->bot_pers, 0, sizeof *p->bot_pers);
+			cr_find_route(p, hit->s.origin, true);
+			Z_Free(p->bot_info);
+			p->bot_info = nil;
+			Z_Free(p->bot_pers);
+			p->bot_pers = nil;
+		}
+	}
+}
+
+void cr_node_keeper_think ( edict_t* self )
+{
+	int      count;
+	edict_t *client;
+	path_node_t *node;
+	path_node_t *next_node;
+
+	self->nextthink = level.time + 0.3f;
+	if (!cr_node_head) cr_init_node_net();
+	cr_check_pending_bots();
+
+	//    rcount=0;
+	node_count=0;
+	count = qrandom()*11;
+	for ( node=cr_node_head; node!=NULL; node=next_node ) {
+		node_count++;
+		next_node = node->next;
+		if (!next_node) break;
+		if (count++<11) continue;
+		if ( !next_node->link_from[2/*MAX_NODE_LINKS/2*/] &&
+			(level.time - next_node->time)>10) {
+			cr_add_links_radius(next_node);
+			next_node->time = level.time;
+			count=0;
+		}
+	}
+	// if ((node_count%10)==0) gi.bprintf ( PRINT_MEDIUM, "nodes: %d  removed: %d\n", node_count, rcount );
+
+	if ( node_count<MAX_NODE_COUNT*(0.7f+0.5f*qrandom()) ) {
+		trace_t trace;
+		vec3_t  dir, start, end;
+		int     flags;
+
+		for ( client=g_edicts+1; client<=(g_edicts+game.maxclients); client++) {
+			if (!client->inuse || !client->client || cistrcmp(client->classname, "player")!=0 ) continue;
+
+			flags = 0;
+			if (client->watertype & (CONTENTS_LAVA|CONTENTS_SLIME)) continue;
+			if (!client->groundentity && client->waterlevel==0) {
+				AngleVectors( client->client->v_angle, dir, NULL, NULL );
+				VectorCopy( client->s.origin, start );
+				VectorMA ( start, 60, dir, end );
+				trace = gi.trace ( start, client->mins, client->maxs, end, client, CONTENTS_LADDER | CONTENTS_SOLID );
+				if ( !(trace.contents & CONTENTS_LADDER) ) continue;
+				flags |= NF_LADDER;
+			}
+
+			cr_register_position( client, flags );
+
+			// cr_debug_draw(client);
+		}
+	}
+}
+
+void cr_init_node_keeper(void)
+{
+	if (cr_node_keeper) return;
+
+	cr_node_keeper = G_Spawn();
+
+	cr_node_keeper->classname = "node keeper";
+	cr_node_keeper->svflags |= SVF_NOCLIENT;
+	cr_node_keeper->solid = SOLID_NOT;
+
+	cr_node_keeper->think = cr_node_keeper_think;
+	cr_node_keeper->nextthink = level.time + 1.5f;
+
+	gi.linkentity(cr_node_keeper);
+}
+
+void cr_join_ctf( edict_t *self )
+{
+	int team;
+
+	if (self->bot_pers->team_no>0) {
+		if (self->bot_pers->team_no&1) team = CTF_TEAM1;
+		else team = CTF_TEAM2;
+		if (self->client->resp.ctf_team==team) return;
+	}
+	else 
+		if ( self->client->resp.ctf_team==CTF_TEAM1 ||
+			self->client->resp.ctf_team==CTF_TEAM2 ) {
+			team = self->client->resp.ctf_team;
+		}
+		else {
+			edict_t *player;
+			int i, team1count = 0, team2count = 0;
+
+			for (i = 1; i <= maxclients->value; i++) {
+				player = &g_edicts[i];
+
+				if (!player->inuse || player==self)
+					continue;
+
+				switch (player->client->resp.ctf_team) {
+				case CTF_TEAM1:
+					team1count++;
+					break;
+				case CTF_TEAM2:
+					team2count++;
+					break;
+				}
+			}
+
+			team = CTF_TEAM1;
+			if (team2count<team1count || 
+				(team2count==team1count && (rand()&1))) team = CTF_TEAM2;
+		}
+
+	CTFJoinTeam(self,team);
+}
+
+
+float last_bot_respawn_time=0;
+
+void cr_respawn( edict_t *self )
+{
+	vec3_t  spawn_origin, spawn_angles;
+	int     i;
+
+	if (level.time<last_bot_respawn_time) last_bot_respawn_time=level.time;
+	if (!cr_node_head || level.time<(last_bot_respawn_time+0.6f)) {
+		self->nextthink = level.time + FRAMETIME;
+		return;
+	}
+	last_bot_respawn_time=level.time;
+
+	if ( strcmp(self->classname,"new_bot")==0 ) {
+		if (self->bot_pers->team_no>0)
+			gi.bprintf ( PRINT_MEDIUM, "%s has entered the game, team: %d\n", 
+				self->client->pers.netname, self->bot_pers->team_no );
+		else
+			gi.bprintf ( PRINT_MEDIUM, "%s has entered the game\n", self->client->pers.netname );
+	}
+
+	if (ctf->value) cr_join_ctf(self);
+
+	// spawn the bot on a spawn spot
+	SelectSpawnPoint( self, spawn_origin, spawn_angles );
+	spawn_origin[2] += 3.f; // make sure off ground
+	self->ideal_yaw = spawn_angles[YAW];
+	VectorCopy ( spawn_origin, self->s.origin );
+	VectorCopy ( self->s.origin, self->s.old_origin );
+
+	// clear playerstate values
+	memset (&self->client->ps, 0, sizeof(self->client->ps));
+
+	self->client->ps.pmove.origin[0] = spawn_origin[0]*8;
+	self->client->ps.pmove.origin[1] = spawn_origin[1]*8;
+	self->client->ps.pmove.origin[2] = spawn_origin[2]*8;
+
+	// set the delta angle
+	for (i=0; i<3; i++)
+		self->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - self->client->resp.cmd_angles[i]);
+	self->s.angles[PITCH] = 0;
+	self->s.angles[YAW] = spawn_angles[YAW];
+	self->s.angles[ROLL] = 0;
+	VectorCopy ( self->s.angles, self->client->ps.viewangles);
+	VectorCopy ( self->s.angles, self->client->v_angle);
+
+	// clear entity values
+	self->classname = "bot";
+	self->groundentity = NULL;
+	self->takedamage = DAMAGE_AIM;
+	self->deadflag = DEAD_NO;
+	self->movetype = MOVETYPE_STEP; //MOVETYPE_NONE;
+	self->mass = 200;
+	self->solid = SOLID_BBOX;
+	self->clipmask = MASK_PLAYERSOLID;
+	self->max_health = 100;
+	self->gib_health = -40;
+	self->waterlevel = 0;
+	self->watertype = 0;
+	self->health = 100;
+	self->max_health = 100;
+	self->viewheight = 22;
+	self->inuse = true;
+	self->yaw_speed = self->bot_pers->rot_speed;
+	self->air_finished = level.time + 12;
+	self->flags &= ~FL_NO_KNOCKBACK;
+	self->svflags &= ~SVF_DEADMONSTER;
+
+	self->bot_info->strafe_dir=1;
+
+	self->bot_info->pickup_target=NULL;
+	self->bot_info->pickup_target_score = 1e32;
+	self->bot_info->last_node=NULL;
+	self->bot_info->next_node=NULL;
+	self->bot_info->target_node=NULL;
+	self->bot_info->time_last_stuck=0;
+	self->bot_info->time_next_enemy=0;
+	self->bot_info->time_next_pickup=0;
+	self->bot_info->time_next_shot=0;
+	self->bot_info->time_last_strafe_switch=0;
+	self->bot_info->old_think = NULL;
+
+	self->bot_info->stuck_count = 0;
+	self->bot_info->move_block_count = 0;
+	self->bot_info->path_nodes = 0;
+
+	self->bot_info->time_next_crouch = level.time + TIME_CROUCH;
+	self->bot_info->time_stuck_check = level.time + TIME_CHECK_STUCK;
+	VectorCopy ( self->s.origin, self->bot_info->old_origin );
+
+	self->bot_info->time_next_fight_message = level.time + FIGHT_MSG_DELAY;
+	self->bot_info->time_last_message=0;
+
+	self->model = self->bot_pers->model;
+	self->s.modelindex = 255; //gi.modelindex(self->model);//255;
+	self->s.skinnum = self->bot_pers->playernum; //self->bot_pers->skin_no; //game.maxclients-1;
+	self->s.frame = 0;
+
+	self->s.modelindex2 = 255;
+	self->s.modelindex3 = 0;    // remove linked ctf flag
+	self->client->newweapon = self->client->pers.weapon;
+	cr_change_weapon (self);
+
+	self->client->anim_priority = ANIM_BASIC;
+	self->client->anim_end = 0;
+
+	self->pain = cr_pain;
+	self->die = cr_die;
+
+	self->think = cr_think;
+	self->nextthink = level.time + FRAMETIME;
+	self->bot_info->postthink = cr_post_think;
+
+	self->touch = NULL; //cr_touch;
+	self->monsterinfo.stand = NULL;
+	self->monsterinfo.walk = NULL;
+	self->monsterinfo.run = NULL;
+	self->monsterinfo.attack = NULL;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = cr_sight;
+	self->monsterinfo.search = NULL;
+	self->monsterinfo.checkattack = NULL;
+
+	self->bot_info->b_crouch = false;
+	VectorSet( self->mins, -16, -16, -24);
+	VectorSet( self->maxs,  16,  16, 32);
+	VectorClear( self->velocity );
+
+	self->s.event = EV_PLAYER_TELEPORT;
+
+	KillBox (self);
+
+	gi.linkentity(self);
+
+	self->client->pers.weapon = FindItem ("blaster");
+	self->client->newweapon = self->client->pers.weapon;
+
+	cr_pick_random_destination(self);
+}
+
+void cr_show_stats( edict_t *self )
+{
+	edict_t *bot;
+
+	gi.cprintf( self, PRINT_MEDIUM, "-------------------------------------------------------\n");
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if (!bot->inuse || !bot->client || !bot->bot_info) continue;
+
+		gi.cprintf( self, PRINT_MEDIUM, "%s -- skill:%d adp:%s team:%d frags:%d\n",
+			bot->client->pers.netname,  
+			bot->bot_pers->skill,
+			bot->bot_pers->b_adapting ? "yes" : "no ",
+			bot->bot_pers->team_no,
+			// bot->bot_pers->skin,
+		// bot->bot_pers->model,
+		bot->client->resp.score );
+	}
+	gi.cprintf( self, PRINT_MEDIUM, "-------------------------------------------------------\n" );
+	// gi.cprintf( self, PRINT_MEDIUM, "total nodes in waypoints network = %d\n\n", node_count );
+}
+
+void cr_show_team_stats(  edict_t *self  )
+{
+	edict_t *bot;
+	int      n, i, j;
+	qboolean b_show;
+	char     total[128];
+
+	gi.cprintf( self, PRINT_MEDIUM, "--------- team scores ----------\n");
+
+	for ( i=1; i<20; i++ ) {
+		n=0;
+		b_show = false;
+		for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+			if ( !bot->inuse || !bot->client || 
+				bot->client->pers.team_no!=i ) continue;
+
+			gi.cprintf( self, PRINT_MEDIUM, "%12s = %d\n",
+				bot->client->pers.netname,  
+				bot->client->resp.score );
+			n += bot->client->resp.score;
+			b_show = true;
+		}
+		if (b_show) {
+			sprintf ( total, "           === %d", n );
+			for ( j=0; j<strlen(total); j++ ) total[j] |= 0x80;
+			gi.cprintf( self, PRINT_MEDIUM, "%s\n\n", total );
+		}
+	}
+}
+
+
+/*
+void cr_scoreboard_message( char* string, edict_t *self )
+{
+#define MAX_SB_BOT 64
+#define MAX_LEN    (1400-1)
+    gclient_t* bot[MAX_SB_BOT], *cl;
+    int        i, j, sl, el, total;
+    edict_t   *client;
+    char       entry[1024];
+
+    total=0;
+    for ( client=g_edicts; client<&g_edicts[globals.num_edicts]; client++ ) {
+         if (!client->client) continue;
+         if (!client->classname) continue;
+         if ( cistrcmp (client->classname, "bot") &&
+              cistrcmp (client->classname, "player") ) continue;
+         bot[total] = client->client;
+         total++;
+         if (total>=MAX_SB_BOT) break;
+         }
+
+    for ( i=0; i<total; i++ ) {
+       sl = i;
+       el = bot[i]->resp.score;
+       for ( j=i+1; j<total; j++ ) {
+          if ( el<bot[j]->resp.score ) {
+            sl = j;
+            el = bot[j]->resp.score;
+            } 
+          }
+       if (sl!=i) {
+         cl = bot[i];
+         bot[i] = bot[sl];
+         bot[sl] = cl;
+         }
+       }
+
+    if (total>16) total=16;
+
+    sl = strlen(string);
+    for ( i=0; i<total; i++ ) {
+       if (self->client==bot[i]) {
+         char str1[80], str2[8], *pch;
+         strncpy(str1,bot[i]->pers.netname,sizeof(str1));
+         for( pch=str1; *pch; pch++) *pch|=0x80;
+         sprintf(str2, "%d", bot[i]->resp.score);
+         for( pch=str2; *pch; pch++) *pch|=0x80;
+         Com_sprintf ( entry, sizeof(entry), 
+                       "xv 170 yv %i string2 \"%s\" " 
+                       "xv 290 yv %i string2 \"%s\" ", 
+                       5+i*9, str1,
+                       5+i*9, str2 );
+         }
+       else {
+         Com_sprintf ( entry, sizeof(entry), 
+                       "xv 170 yv %i string2 \"%s\" " 
+                       "xv 290 yv %i string2 \"%i\" ", 
+                       5+i*9, bot[i]->pers.netname,
+                       5+i*9, bot[i]->resp.score );
+         }
+       el = strlen(entry);
+       if ( (sl+el)>=MAX_LEN ) break;
+       strcpy( string+sl, entry );
+       sl += el;
+       }
+
+}
+*/
+
+void cr_get_full_pathname( char* fullname, char* name )
+{
+	char* pc;
+
+	strcpy( fullname, base_path->string );
+	pc = fullname + strlen(fullname)-1;
+	while (pc>=fullname && *pc==' ') *pc--='\0';
+	if (*pc) {
+		if (*pc!='/') *++pc='/';
+		*++pc='\0';
+	}
+	strcpy( pc, game_path->string );
+	pc+=strlen(game_path->string);
+	strcpy( pc, "/" );
+	pc++;
+	strcpy( pc, name );
+}
+
+qboolean
+cr_load_message_file(char** msg_array, int* msg_count, char* filename)
+{
+	FILE  *f;
+	char  msg[MAX_MSG_LEN];
+	int   n;
+
+	/* already freed ffs */
+	//for(i=0; i<*msg_count; i++)
+	//	Z_Free(msg_array[i]);
+	*msg_count=0;
+
+	cr_get_full_pathname( msg, filename );
+	f = fopen( msg, "rt" );
+	if (!f) {
+		gi.dprintf( "ERROR loading %s\n", msg );
+		return false;
+	}
+
+	while (1) {
+		if (!fgets( msg, sizeof(msg), f )) break;
+		n = strlen(msg);
+		while(n>0) {
+			if (msg[n-1]!='\r' && msg[n-1]!='\n') break;
+			n--;
+			msg[n]='\0';
+		}
+		if (n<=0) continue;
+		msg_array[*msg_count] = gi.TagMalloc( n+2, TAG_GAME );
+		strcpy( msg_array[*msg_count], msg );
+		(*msg_count)++;
+		if (*msg_count>=MAX_MSG) break;
+	}
+
+	fclose (f);
+
+	return true;
+}
+
+
+void cr_init(void)
+{
+	//path_node_t *node, *next_node;
+
+	gi.dprintf ( "-_-_-_-_-_- CRBot init -_-_-_-_-_-\n" );
+
+	cr_init_variables();
+
+	cr_load_message_file( msg_pain, &msg_pain_count, "pain.txt" );
+	cr_load_message_file( msg_kill, &msg_kill_count, "kill.txt" );
+	cr_load_message_file( msg_death, &msg_death_count, "death.txt" );
+	cr_load_message_file( msg_fight, &msg_fight_count, "fight.txt" );
+	cr_load_maplist();
+
+	sound_footstep[0] = gi.soundindex ("player/step1.wav");
+	sound_footstep[1] = gi.soundindex ("player/step2.wav");
+	sound_footstep[2] = gi.soundindex ("player/step3.wav");
+	sound_footstep[3] = gi.soundindex ("player/step4.wav");
+
+	// free old node-net
+	/* already freed in GameShutdown, christ
+    for ( node=cr_node_head; node!=NULL; node=next_node ) {
+       next_node=node->next;
+       gi.TagFree(node);
+       }
+*/
+	cr_node_head=NULL;
+
+	if (cr_node_keeper) {
+		G_FreeEdict(cr_node_keeper);
+		cr_node_keeper=NULL;
+	}
+
+	cr_init_node_keeper();
+
+	global_bot_number = 1;
+}
+
+void cr_remove_bot( edict_t *bot )
+{
+	// send effect
+	gi.WriteByte( svc_muzzleflash );
+	gi.WriteShort( bot-g_edicts );
+	gi.WriteByte( MZ_LOGOUT );
+	gi.multicast( bot->s.origin, MULTICAST_PVS );
+
+	gi.bprintf ( PRINT_HIGH, "%s disconnected\n", bot->client->pers.netname );
+	cr_free_bot( bot );
+}
+
+void cr_kill_bot( char* bot_name )
+{
+	edict_t *hit, *bot;
+
+	bot = NULL;
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse || !hit->client) continue;
+		if (cistrcmp (hit->classname, "bot")) continue;
+		if (cistrcmp ( hit->client->pers.netname, bot_name )) continue;
+		bot = hit;
+		break;
+	}
+	if (bot) {
+		for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+			if (!hit->inuse || !hit->client) continue;
+			if (cistrcmp (hit->classname, "bot")) continue;
+			if (hit->enemy==bot) {
+				hit->enemy=NULL;
+				hit->oldenemy=NULL;
+			}
+		}
+		cr_unregister_bot(bot->bot_pers);
+		cr_remove_bot( bot );
+	}
+}
+
+void cr_client_connect(edict_t *ent)
+{
+	int      i, playernum, pn;
+	edict_t* bot;
+	qboolean old_inuse;
+
+	playernum = ent-g_edicts-1;
+	old_inuse = ent->inuse;
+	ent->inuse = true;
+
+	for ( bot=g_edicts+1; bot<&g_edicts[globals.num_edicts]; bot++) {
+		if (!bot->inuse || !bot->bot_pers) continue;
+		if (bot->bot_pers->playernum==playernum) {
+			// find new playernum 
+			/*
+         pn = cr_find_unused_client_slot();
+         if (pn<0) {
+           bot->client->inuse = false;
+           cr_remove_bot( bot );
+           }
+         else {
+           gclient_t* old_client = bot->client;
+           bot->client = &game.clients[pn];
+           *bot->client = *old_client;
+           old_client->inuse = false;
+           bot->client->inuse = true;
+           bot->bot_pers->playernum = pn;
+           cr_update_userinfo(bot->bot_pers);
+           }
+*/
+			pn = -1;
+			bot->client->inuse = false;
+			cr_remove_bot( bot );
+			// correct playernum in bot's list
+			for ( i=0; i<global_bots_count; i++ ) {
+				if (!global_bots[i].b_inuse) continue;
+				if (global_bots[i].playernum==playernum) {
+					global_bots[i].playernum=pn;
+				}
+			}
+			break;
+		}
+	}
+
+	ent->inuse = old_inuse;
+}
+
+void
+cr_init_globals(void)
+{
+	int size;
+
+	global_bots_count = game.maxclients;
+
+	/* already freed by ShutownGame */
+	//if(global_bots != nil)
+	//	Z_Free(global_bots);
+
+	size = global_bots_count * sizeof *global_bots;
+	global_bots = Z_TagMalloc(size, TAG_GAME);
+	memset(global_bots, 0, size);
+}
+
+void cr_exit_level(void)
+{
+	int      i;
+	edict_t* bot;
+
+	// mark all bots to respawn on new level
+	for ( i=0; i<global_bots_count; i++ ) {
+		if (!global_bots[i].b_inuse) continue;
+		game.clients[global_bots[i].playernum].inuse = false;
+		global_bots[i].playernum = -1;
+	}
+
+	for ( bot=g_edicts+1; bot<=(g_edicts+game.maxclients); bot++) {
+		if (!bot->inuse || !bot->client || !bot->bot_info) continue;
+		cr_free_bot( bot );
+	}
+}
+
+void cr_load_maplist(void)
+{
+	FILE  *f;
+	char  line[256];
+	int   n;
+
+	map_cycle_count=0;
+
+	cr_get_full_pathname( line, "maplist.txt" );
+	f = fopen( line, "rt" );
+	if (!f) {
+		gi.dprintf( "ERROR loading maplist.txt\n" );
+		return;
+	}
+
+	while (map_cycle_count<(MAX_MAP-1)) {
+		if (!fgets( line, sizeof(line), f )) break;
+		if (!*line || *line=='#' || *line==';' || *line=='/' ||
+			*line=='\\' || *line==' ' || *line=='\r' || *line=='\n' ) continue;
+		n = strlen(line);
+		while(n>0) {
+			if (line[n-1]!='\r' && line[n-1]!='\n') break;
+			n--;
+			line[n]='\0';
+		}
+		if (n<=0) continue;
+		strncpy( (char*)(map_list + map_cycle_count++), line, 32 );
+	}
+
+	fclose (f);
+	next_map %= map_cycle_count;
+}
+
+char next_map_name[64]="\0";
+
+qboolean cr_map_cycle(void)
+{
+	int new_map;
+	if (!(int)map_cycle->value || map_cycle_count<2) return false;
+
+	switch ((int)map_cycle->value) {
+	case 2:
+		do {
+			new_map = rand() % map_cycle_count;
+		} while( new_map==next_map );
+		next_map = new_map;
+		break;
+	default:
+		next_map = (next_map+1) % map_cycle_count;
+	}
+
+	next_map %= map_cycle_count;
+	strcpy( next_map_name, (char*)(map_list + next_map) );
+
+	return true;
+}
+
+void cr_check_ctf_routes( edict_t *self )
+{
+	edict_t *hit;
+	edict_t *flag1=NULL, *flag2=NULL;
+	vec3_t   old_origin;
+
+	for ( hit=g_edicts+1; hit<&g_edicts[globals.num_edicts]; hit++) {
+		if (!hit->inuse) continue;
+		if (hit->spawnflags & DROPPED_ITEM) continue;
+		// if ((hit->svflags & SVF_NOCLIENT) || (hit->solid==SOLID_NOT)) continue;
+
+		if (hit->item && hit->item->pickup==CTFPickup_Flag) {
+			if (!flag1) flag1=hit;
+			else {
+				flag2=hit;
+				break;
+			}
+		}
+	}
+
+	if (!flag1) {
+		gi.dprintf("Warning: No flags found on this map!\n");
+		return;
+	}
+	if (!flag2) {
+		gi.dprintf("Warning: Only one flag found on this map!\n");
+		return;
+	}
+
+	VectorCopy( self->s.origin, old_origin );
+	VectorCopy( flag1->s.origin, self->s.origin );
+
+	self->bot_info = gi.TagMalloc ( sizeof(bot_info_t), TAG_GAME );
+	memset( self->bot_info, 0, sizeof(bot_info_t) );
+	self->bot_pers = gi.TagMalloc ( sizeof(bot_info_pers_t), TAG_GAME );
+	memset(self->bot_pers,0,sizeof(bot_info_pers_t));
+	self->bot_pers->skill = 100;
+
+	if (!cr_find_route( self, flag2->s.origin, true )) {
+		gi.dprintf("Warning: No route from one flag to another!\n");
+	}
+
+	gi.TagFree(self->bot_info);
+	self->bot_info=NULL;
+	gi.TagFree(self->bot_pers);
+	self->bot_pers=NULL;
+
+	VectorCopy( old_origin, self->s.origin );
+}
+
+
+char file_signature[] = "CRBOT.ROUTE.MAP.01.";
+
+void cr_routes_save( edict_t *self )
+{
+	path_node_t *node;
+	FILE        *f;
+	char         filename[256], rt_name[80];
+	int          reserved[20], total, saved;
+
+	if (ctf->value) {
+		cr_check_ctf_routes(self);
+	}
+
+	cr_get_full_pathname( filename, "nodemaps" );
+	mkdir(filename);
+
+	sprintf( rt_name, "nodemaps%s%s.crn", "/", level.mapname );
+	cr_get_full_pathname( filename, rt_name );
+	f = fopen( filename, "wb" );
+	if (!f) {
+		gi.dprintf( "ERROR creating file: %s\n", rt_name );
+		return;
+	}
+
+	if (!fwrite(file_signature,sizeof(file_signature),1,f)) {
+		gi.dprintf( "ERROR writing file: %s\n", rt_name );
+		fclose(f);
+		return;
+	}
+
+	memset(filename,0,256);
+	if (!fwrite(filename,256,1,f)) {
+		gi.dprintf( "ERROR writing file: %s\n", rt_name );
+		fclose(f);
+		return;
+	}
+
+	// save nodes
+	memset(reserved,0,sizeof(reserved));
+	total=saved=0;
+	for ( node=cr_node_head; node!=NULL; node=node->next ) {
+		total++;
+		if (node->item) continue;
+		saved++;
+		if (!fwrite(reserved,sizeof(reserved),1,f)) {
+			gi.dprintf( "ERROR writing file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+		if (!fwrite(node->position,sizeof(node->position),1,f)) {
+			gi.dprintf( "ERROR writing file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+		if (!fwrite(&node->flags,sizeof(node->flags),1,f)) {
+			gi.dprintf( "ERROR friting file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+	}
+
+	reserved[0]=1;
+	if (!fwrite(reserved,sizeof(reserved),1,f)) {
+		gi.dprintf( "ERROR writing file: %s\n", rt_name );
+		fclose(f);
+		return;
+	}
+
+	fclose(f);
+
+	gi.dprintf( "total nodes: %d,  saved: %d\n", total, saved );
+}
+
+void cr_routes_load(void)
+{
+	vec3_t pos;
+	int    flags;
+	FILE  *f;
+	char   filename[256], rt_name[80];
+	int    reserved[20], total;
+
+	sprintf( rt_name, "nodemaps%s%s.crn", "/", level.mapname );
+	cr_get_full_pathname( filename, rt_name );
+	f = fopen( filename, "rb" );
+	if (!f) return;
+
+	memset(filename,0,256);
+	if (!fread(filename,sizeof(file_signature),1,f)) {
+		gi.dprintf( "ERROR reading file: %s\n", rt_name );
+		fclose(f);
+		return;
+	}
+	if (strcmp(filename,file_signature)) return;
+
+	if (!fread(filename,256,1,f)) {
+		gi.dprintf( "ERROR reading file: %s\n", rt_name );
+		fclose(f);
+		return;
+	}
+
+	//read nodes
+	total=0;
+	while(true) {
+		total++;
+		if (!fread(reserved,sizeof(reserved),1,f)) {
+			gi.dprintf( "ERROR reading file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+		if (reserved[0]) break;
+		if (!fread(pos,sizeof(pos),1,f)) {
+			gi.dprintf( "ERROR reading file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+		if (!fread(&flags,sizeof(flags),1,f)) {
+			gi.dprintf( "ERROR reading file: %s\n", rt_name );
+			fclose(f);
+			return;
+		}
+
+		cr_insert_node( pos, NULL, flags );
+	}
+
+	fclose(f);
+
+	gi.dprintf( "total nodes read from nodemap file: %d\n", total );
+}
--- /dev/null
+++ b/crbot/cr_menu.c
@@ -1,0 +1,375 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void SP_crbot( char* name, int skill_level, char* skin, int team, char* model );
+void cr_show_stats( edict_t *self );
+
+#define MENU_MAIN     1
+#define MENU_ADD_BOT  2
+
+#define MI_MAIN_ADDBOT      1
+#define MI_MAIN_STATS       2
+#define MI_MAIN_TEAMS       3
+#define MI_MAIN_MYTEAM      4
+#define MI_MAIN_OBITUARY    5
+#define MI_MAIN_BOTCHAT     6
+#define MI_MAIN_TAUNT       7
+#define MI_MAIN_MAPCYCLE    8
+#define MI_MAIN_TECHS       9
+#define MI_MAIN_HOOK        10
+#define MI_MAIN_SAVENODES   11
+#define MI_MAIN_MAX         11
+
+#define MI_AB_PREVMENU   1
+#define MI_AB_SKILL      2
+#define MI_AB_MODEL      3
+#define MI_AB_SKIN       4
+#define MI_AB_TEAM       5 
+#define MI_AB_ADDBOT     6
+#define MI_AB_MAX        6
+
+#define MAX_SKIN  1024
+#define MAX_MODEL 128
+
+typedef char stname[32];
+
+stname skins[MAX_SKIN], models[MAX_MODEL];
+int    nmodel=0, curmd=0, curskin=0;
+int    skhead[MAX_MODEL], nskin[MAX_MODEL];
+
+void cr_invert_string( char* pc )
+{
+	while(*pc!='"') {
+		*pc |= 0x80;
+		pc++;
+	}
+}
+
+/* FIXME: bullshit */
+void cr_menu_init(void)
+{
+	char mdir[256], sdir[512], *p;
+	int fd, nmd, nsk, mdi, sdi, l, n, s_count, s_start = 0;
+	Dir *md = nil, *sk = nil;
+
+	memset(skins, 0, sizeof skins);
+	memset(models, 0, sizeof models);
+	seprint(mdir, mdir+sizeof mdir, "%s/baseq2/players", base_path->string);
+
+	if((fd = open(mdir, OREAD)) < 0){
+		fprint(2, "cr_menu_init:open: %r\n");
+		goto fail;
+	}
+	nmd = dirreadall(fd, &md);
+	close(fd);
+	if(nmd <= 0){
+		fprint(2, "cr_menu_init:dirreadall: %r\n");
+		goto fail;
+	}
+	if(nmd > MAX_MODEL){
+		fprint(2, "cr_menu_init: too many models\n");
+		goto fail;
+	}
+
+	/* get models and skins */
+	nmodel = 0;
+	for(mdi=0; mdi < nmd; mdi++){
+		/* [words]
+			return thisdir->d_name[0] != '.';
+			//return thisdir->d_type == DT_DIR; //always returns zero ?
+		*/
+		if(~md[mdi].mode & DMDIR)
+			continue;
+
+		strncpy(models[nmodel], md[mdi].name, sizeof *models);
+		seprint(sdir, sdir+sizeof sdir, "%s/%s", mdir, models[nmodel]);
+		if((fd = open(sdir, OREAD)) < 0){
+			fprint(2, "cr_menu_init:open: %r\n");
+			goto fail;
+		}
+		nsk = dirreadall(fd, &sk);
+		close(fd);
+		if(nsk <= 0){
+			fprint(2, "cr_menu_init:dirreadall: %r\n");
+			continue;
+		}
+
+		s_count = 0;
+		for(sdi = 0; sdi < nsk; sdi++){
+			if(strstr(sk[sdi].name, "_i.pcx") == nil)
+				continue;
+			p = skins[s_start+s_count];
+			memset(p, 0, sizeof *p);
+			l = strlen(sk[sdi].name) - 6;
+			if(l > sizeof *p)
+				l = sizeof *p;
+			strncpy(p, sk[sdi].name, l);
+			s_count++;
+		}
+		free(sk);
+		skhead[nmodel] = s_start;
+		nskin[nmodel] = s_count;
+		s_start += s_count;
+		nmodel++;
+	}
+	free(md);
+
+	for(n=0; n < nmodel; n++)
+		if(!cistrcmp(bot_model->string, models[n])){
+			curmd = n;
+			break;
+		}
+	for(n=0; n < nskin[curmd]; n++)
+		if(!cistrcmp(bot_skin->string, skins[skhead[curmd]+n])){
+			curskin = n;
+			break;
+		}
+	gi.cvar_forceset("bot_model", models[curmd]);
+	gi.cvar_forceset("bot_skin", skins[skhead[curmd]+curskin]);
+	return;
+fail:
+	free(md);
+	free(sk);
+	gi.error("failed to init models and skins");
+}
+
+void cr_menu_draw( edict_t *self )
+{
+	char  menu[1400], *pch;
+	gclient_t *cl;
+
+	cl = self->client;
+	if (!cl || self->bot_info) return;
+
+	if (cl->menu_no<=0) cl->menu_no=1;
+	if (cl->menu_item_no<=0) cl->menu_item_no=0;
+
+	pch = menu;
+	sprintf ( pch, "xv 32 yv 8 picn inventory " );
+	pch += strlen(pch);
+
+	switch (cl->menu_no) {
+	case MENU_MAIN:
+		sprintf( pch, "xv 0 yv 30 cstring2 \"[CRBot] main menu:\" "
+			"xv 0 yv 32 cstring2 \"__________________\" " );
+		break;
+	case MENU_ADD_BOT:
+		sprintf( pch, "xv 0 yv 30 cstring2 \"[CRBot] add new bot:\" "
+			"xv 0 yv 32 cstring2 \"____________________\" " );
+		break;
+	default:
+		cl->menu_no=0;
+		return;
+	};
+	pch += strlen(pch);
+
+	switch (cl->menu_no) {
+	case MENU_MAIN:
+		sprintf( pch, "xv 56 yv 50 string2 \"add new bot...\" " );
+		if (cl->menu_item_no!=MI_MAIN_ADDBOT) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 60 string2 \"show bots statistics...\" " );
+		if (cl->menu_item_no!=MI_MAIN_STATS) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 70 string2 \"show team scores...\" " );
+		if (cl->menu_item_no!=MI_MAIN_TEAMS) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		if (self->client->pers.team_no)
+			sprintf( pch, "xv 56 yv 80 string2 \"player's team     = %d\" ", self->client->pers.team_no );
+		else
+			sprintf( pch, "xv 56 yv 80 string2 \"player's team     = none\" " );
+		if (cl->menu_item_no!=MI_MAIN_MYTEAM) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 90 string2 \"obituary messages = %s\" ", obituary_msgs->value ? "show" : "skip" );
+		if (cl->menu_item_no!=MI_MAIN_OBITUARY) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 100 string2 \"bot chat          = %s\" ", bot_chat->value ? "on" : "off" );
+		if (cl->menu_item_no!=MI_MAIN_BOTCHAT) cr_invert_string(pch+22);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 110 string2 \"bot taunts        = %s\" ", bot_taunt->value ? "on" : "off" );
+		if (cl->menu_item_no!=MI_MAIN_TAUNT) cr_invert_string(pch+22);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 120 string2 \"map cycle         = %s\" ", ((int)map_cycle->value)==2 ? "random" : ((int)map_cycle->value)==1 ? "on" : "off" );
+		if (cl->menu_item_no!=MI_MAIN_MAPCYCLE) cr_invert_string(pch+22);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 130 string2 \"techs in dm game  = %s\" ", !((int)no_tech->value) ? "enable" : "disable" );
+		if (cl->menu_item_no!=MI_MAIN_TECHS) cr_invert_string(pch+22);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 140 string2 \"g.hook in dm game = %s\" ", !((int)no_hook->value) ? "enable" : "disable" );
+		if (cl->menu_item_no!=MI_MAIN_HOOK) cr_invert_string(pch+22);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 150 string2 \"save nodemap file...\" " );
+		if (cl->menu_item_no!=MI_MAIN_SAVENODES) cr_invert_string(pch+22);
+		break;
+	case MENU_ADD_BOT:
+		sprintf( pch, "xv 56 yv 50 string2 \"...back to main menu...\" " );
+		if (cl->menu_item_no!=MI_AB_PREVMENU) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		if (bot_skill->value)
+			sprintf( pch, "xv 56 yv 60 string2 \"skill  = %d\" ", (int)bot_skill->value );
+		else
+			sprintf( pch, "xv 56 yv 60 string2 \"skill  = adapting\" " );
+		if (cl->menu_item_no!=MI_AB_SKILL) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 70 string2 \"model   = %s\" ", bot_model->string );
+		if (cl->menu_item_no!=MI_AB_MODEL) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 80 string2 \"skin    = %s\" ", bot_skin->string );
+		if (cl->menu_item_no!=MI_AB_SKIN) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		if (bot_team->value)
+			sprintf( pch, "xv 56 yv 90 string2 \"team    = %d\" ", (int)bot_team->value );
+		else
+			sprintf( pch, "xv 56 yv 90 string2 \"team    = none\" " );
+		if (cl->menu_item_no!=MI_AB_TEAM) cr_invert_string(pch+21);
+		pch += strlen(pch);
+		sprintf( pch, "xv 56 yv 100 string2 \"-> add new bot <-\" " );
+		if (cl->menu_item_no!=MI_AB_ADDBOT) cr_invert_string(pch+22);
+		break;
+	};
+
+	gi.configstring ( CS_STATUSBAR, menu );
+}
+
+
+void cr_menu_select( edict_t *self, int ds )
+{
+	gclient_t *cl;
+	int        no;
+	char       buf[8];
+
+	cl = self->client;
+	if (!cl || self->bot_info) return;
+
+	switch (cl->menu_no) {
+	case MENU_MAIN:
+		switch (cl->menu_item_no) {
+		case MI_MAIN_ADDBOT:
+			cl->menu_no = MENU_ADD_BOT;
+			cl->menu_item_no = 1;
+			break;
+		case MI_MAIN_OBITUARY:
+			gi.cvar_forceset( "obituary_msgs", obituary_msgs->value ? "0" : "1" );
+			break;
+		case MI_MAIN_MYTEAM:
+			self->client->pers.team_no += ds;
+			if (self->client->pers.team_no>10) self->client->pers.team_no=0;
+			if (self->client->pers.team_no<0) self->client->pers.team_no=10;
+			break;
+		case MI_MAIN_STATS:
+			cr_show_stats(self);
+			break;
+		case MI_MAIN_TEAMS:
+			cr_show_team_stats(self);
+			break;
+		case MI_MAIN_BOTCHAT:
+			gi.cvar_forceset( "bot_chat", bot_chat->value ? "0" : "1" );
+			break;
+		case MI_MAIN_TAUNT:
+			gi.cvar_forceset( "bot_taunt", bot_taunt->value ? "0" : "1" );
+			break;
+		case MI_MAIN_MAPCYCLE:
+			gi.cvar_forceset( "map_cycle", va( "%d", ((int)map_cycle->value+ds+3)%3 ) );
+			break;
+		case MI_MAIN_TECHS:
+			gi.cvar_forceset( "no_tech", no_tech->value ? "0" : "1" );
+			break;
+		case MI_MAIN_HOOK:
+			gi.cvar_forceset( "no_hook", no_hook->value ? "0" : "1" );
+			break;
+		case MI_MAIN_SAVENODES:
+			cr_routes_save(self);
+			break;
+		}
+		break;
+
+	case MENU_ADD_BOT:
+		switch (cl->menu_item_no) {
+		case MI_AB_PREVMENU:
+			cl->menu_no = MENU_MAIN;
+			cl->menu_item_no = 1;
+			break;
+		case MI_AB_MODEL:
+			curmd+=ds;
+			if (curmd>=nmodel) curmd=0;
+			if (curmd<0) curmd=nmodel-1;
+			gi.cvar_forceset( "bot_model", models[curmd] );
+			if (curskin>=nskin[curmd])
+				curskin = nskin[curmd]-1;
+			gi.cvar_forceset( "bot_skin", skins[skhead[curmd]+curskin] );
+			break;
+		case MI_AB_SKIN:
+			if (curmd>=nmodel) curmd=nmodel-1;
+			curskin+=ds;
+			if (curskin>=nskin[curmd]) curskin=0;
+			if (curskin<0) curskin = nskin[curmd]-1;
+			gi.cvar_forceset( "bot_skin", skins[skhead[curmd]+curskin] );
+			break;
+		case MI_AB_TEAM:
+			no = ((int)bot_team->value + ds + 10) % 10;
+			sprintf(buf,"%d",no);
+			gi.cvar_forceset( "bot_team", buf );
+			break;
+		case MI_AB_SKILL:
+			no = ((int)bot_skill->value + ds + 10) % 10;
+			sprintf(buf,"%d",no);
+			gi.cvar_forceset( "bot_skill", buf );
+			break;
+		case MI_AB_ADDBOT:
+			SP_crbot( NULL, bot_skill->value, bot_skin->string, bot_team->value, bot_model->string );
+			break;
+		}
+		break;
+	}
+
+	cr_menu_draw(self);
+}
+
+void cr_menu_up( edict_t *self )
+{
+	gclient_t *cl;
+
+	cl = self->client;
+	if (!cl || self->bot_info) return;
+
+	cl->menu_item_no--;
+	if (cl->menu_item_no<=0)
+		switch (cl->menu_no) {
+		case MENU_MAIN:
+			cl->menu_item_no=MI_MAIN_MAX;
+			break;
+		case MENU_ADD_BOT:
+			cl->menu_item_no=MI_AB_MAX;
+			break;
+		default:
+			cl->menu_item_no=1;
+		}
+
+	cr_menu_draw(self);
+}
+
+void cr_menu_down( edict_t *self )
+{
+	gclient_t *cl;
+
+	cl = self->client;
+	if (!cl || self->bot_info) return;
+
+	cl->menu_item_no++;
+	switch (cl->menu_no) {
+	case MENU_MAIN:
+		if (cl->menu_item_no>MI_MAIN_MAX) cl->menu_item_no=1;
+		break;
+	case MENU_ADD_BOT:
+		if (cl->menu_item_no>MI_AB_MAX) cl->menu_item_no=1;
+		break;
+	default:
+		cl->menu_item_no=1;
+	}
+
+	cr_menu_draw(self);
+}
+
--- /dev/null
+++ b/crbot/g_ai.c
@@ -1,0 +1,1102 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+qboolean FindTarget (edict_t *self);
+extern cvar_t	*maxclients;
+
+qboolean ai_checkattack (edict_t *self, float dist);
+
+qboolean	enemy_vis;
+qboolean	enemy_infront;
+int			enemy_range;
+float		enemy_yaw;
+
+//============================================================================
+
+
+/*
+=================
+AI_SetSightClient
+
+Called once each frame to set level.sight_client to the
+player to be checked for in findtarget.
+
+If all clients are either dead or in notarget, sight_client
+will be null.
+
+In coop games, sight_client will cycle between the clients.
+=================
+*/
+void AI_SetSightClient (void)
+{
+	edict_t	*ent;
+	int		start, check;
+
+	if (level.sight_client == NULL)
+		start = 1;
+	else
+		start = level.sight_client - g_edicts;
+
+	check = start;
+	while (1)
+	{
+		check++;
+		if (check > game.maxclients)
+			check = 1;
+		ent = &g_edicts[check];
+		if (ent->inuse
+			&& ent->health > 0
+			&& !(ent->flags & FL_NOTARGET) )
+		{
+			level.sight_client = ent;
+			return;		// got one
+		}
+		if (check == start)
+		{
+			level.sight_client = NULL;
+			return;		// nobody to see
+		}
+	}
+}
+
+//============================================================================
+
+/*
+=============
+ai_move
+
+Move the specified distance at current facing.
+This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
+==============
+*/
+void ai_move (edict_t *self, float dist)
+{
+	M_walkmove (self, self->s.angles[YAW], dist);
+}
+
+
+/*
+=============
+ai_stand
+
+Used for standing around and looking for players
+Distance is for slight position adjustments needed by the animations
+==============
+*/
+void ai_stand (edict_t *self, float dist)
+{
+	vec3_t	v;
+
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		if (self->enemy)
+		{
+			VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+			self->ideal_yaw = vectoyaw(v);
+			if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+			{
+				self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+				self->monsterinfo.run (self);
+			}
+			M_ChangeYaw (self);
+			ai_checkattack (self, 0);
+		}
+		else
+			FindTarget (self);
+		return;
+	}
+
+	if (FindTarget (self))
+		return;
+	
+	if (level.time > self->monsterinfo.pausetime)
+	{
+		self->monsterinfo.walk (self);
+		return;
+	}
+
+	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.idle (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_walk
+
+The monster is walking it's beat
+=============
+*/
+void ai_walk (edict_t *self, float dist)
+{
+	M_MoveToGoal (self, dist);
+
+	// check for noticing a player
+	if (FindTarget (self))
+		return;
+
+	if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.search (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_charge
+
+Turns towards target and advances
+Use this call with a distnace of 0 to replace ai_face
+==============
+*/
+void ai_charge (edict_t *self, float dist)
+{
+	vec3_t	v;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+	self->ideal_yaw = vectoyaw(v);
+	M_ChangeYaw (self);
+
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+}
+
+
+/*
+=============
+ai_turn
+
+don't move, but turn towards ideal_yaw
+Distance is for slight position adjustments needed by the animations
+=============
+*/
+void ai_turn (edict_t *self, float dist)
+{
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (FindTarget (self))
+		return;
+	
+	M_ChangeYaw (self);
+}
+
+
+/*
+
+.enemy
+Will be world if not currently angry at anyone.
+
+.movetarget
+The next path spot to walk toward.  If .enemy, ignore .movetarget.
+When an enemy is killed, the monster will try to return to it's path.
+
+.hunt_time
+Set to time + something when the player is in sight, but movement straight for
+him is blocked.  This causes the monster to use wall following code for
+movement direction instead of sighting on the player.
+
+.ideal_yaw
+A yaw angle of the intended direction, which will be turned towards at up
+to 45 deg / state.  If the enemy is in view and hunt_time is not active,
+this will be the exact line towards the enemy.
+
+.pausetime
+A monster will leave it's stand state and head towards it's .movetarget when
+time > .pausetime.
+
+walkmove(angle, speed) primitive is all or nothing
+*/
+
+/*
+=============
+range
+
+returns the range catagorization of an entity reletive to self
+0	melee range, will become hostile even if back is turned
+1	visibility and infront, or visibility and show hostile
+2	infront and show hostile
+3	only triggered by damage
+=============
+*/
+int range (edict_t *self, edict_t *other)
+{
+	vec3_t	v;
+	float	len;
+
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	len = VectorLength (v);
+	if (len < MELEE_DISTANCE)
+		return RANGE_MELEE;
+	if (len < 500)
+		return RANGE_NEAR;
+	if (len < 1000)
+		return RANGE_MID;
+	return RANGE_FAR;
+}
+
+/*
+=============
+visible
+
+returns 1 if the entity is visible to self, even if not infront ()
+=============
+*/
+qboolean visible (edict_t *self, edict_t *other)
+{
+	vec3_t	spot1;
+	vec3_t	spot2;
+	trace_t	trace;
+
+	VectorCopy (self->s.origin, spot1);
+	spot1[2] += self->viewheight;
+	VectorCopy (other->s.origin, spot2);
+	spot2[2] += other->viewheight;
+	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
+	
+	if (trace.fraction == 1.0)
+		return true;
+	return false;
+}
+
+
+/*
+=============
+infront
+
+returns 1 if the entity is in front (in sight) of self
+=============
+*/
+qboolean infront (edict_t *self, edict_t *other)
+{
+	vec3_t	vec;
+	float	dot;
+	vec3_t	forward;
+	
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	dot = DotProduct (vec, forward);
+	
+	if (dot > 0.3)
+		return true;
+	return false;
+}
+
+
+//============================================================================
+
+void HuntTarget (edict_t *self)
+{
+	vec3_t	vec;
+
+	self->goalentity = self->enemy;
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.stand (self);
+	else
+		self->monsterinfo.run (self);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	self->ideal_yaw = vectoyaw(vec);
+	// wait a while before first attack
+	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+		AttackFinished (self, 1);
+}
+
+void FoundTarget (edict_t *self)
+{
+	// let other monsters see this monster for a while
+	if (self->enemy->client)
+	{
+		level.sight_entity = self;
+		level.sight_entity_framenum = level.framenum;
+		level.sight_entity->light_level = 128;
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+	self->monsterinfo.trail_time = level.time;
+
+	if (!self->combattarget)
+	{
+		HuntTarget (self);
+		return;
+	}
+
+	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
+	if (!self->movetarget)
+	{
+		self->goalentity = self->movetarget = self->enemy;
+		HuntTarget (self);
+		gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
+		return;
+	}
+
+	// clear out our combattarget, these are a one shot deal
+	self->combattarget = NULL;
+	self->monsterinfo.aiflags |= AI_COMBAT_POINT;
+
+	// clear the targetname, that point is ours!
+	self->movetarget->targetname = NULL;
+	self->monsterinfo.pausetime = 0;
+
+	// run for it
+	self->monsterinfo.run (self);
+}
+
+
+/*
+===========
+FindTarget
+
+Self is currently not attacking anything, so try to find a target
+
+Returns TRUE if an enemy was sighted
+
+When a player fires a missile, the point of impact becomes a fakeplayer so
+that monsters that see the impact will respond as if they had seen the
+player.
+
+To avoid spending too much time, only a single client (or fakeclient) is
+checked each frame.  This means multi player games will have slightly
+slower noticing monsters.
+============
+*/
+qboolean FindTarget (edict_t *self)
+{
+	edict_t		*client;
+	qboolean	heardit;
+	int			r;
+
+	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
+		{
+			if (strcmp(self->goalentity->classname, "target_actor") == 0)
+				return false;
+		}
+
+		//FIXME look for monsters?
+		return false;
+	}
+
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+		return false;
+
+// if the first spawnflag bit is set, the monster will only wake up on
+// really seeing the player, not another monster getting angry or hearing
+// something
+
+// revised behavior so they will wake up if they "see" a player make a noise
+// but not weapon impact/explosion noises
+
+	heardit = false;
+	if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sight_entity;
+		if (client->enemy == self->enemy)
+		{
+			return false;
+		}
+	}
+	else if (level.sound_entity_framenum >= (level.framenum - 1))
+	{
+		client = level.sound_entity;
+		heardit = true;
+	}
+	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sound2_entity;
+		heardit = true;
+	}
+	else
+	{
+		client = level.sight_client;
+		if (!client)
+			return false;	// no clients to get mad at
+	}
+
+	// if the entity went away, forget it
+	if (!client->inuse)
+		return false;
+
+	if (client == self->enemy)
+		return true;	// JDC false;
+
+	if (client->client)
+	{
+		if (client->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (client->svflags & SVF_MONSTER)
+	{
+		if (!client->enemy)
+			return false;
+		if (client->enemy->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (heardit)
+	{
+		if (client->owner->flags & FL_NOTARGET)
+			return false;
+	}
+	else
+		return false;
+
+	if (!heardit)
+	{
+		r = range (self, client);
+
+		if (r == RANGE_FAR)
+			return false;
+
+// this is where we would check invisibility
+
+		// is client in an spot too dark to be seen?
+		if (client->light_level <= 5)
+			return false;
+
+		if (!visible (self, client))
+		{
+			return false;
+		}
+
+		if (r == RANGE_NEAR)
+		{
+			if (client->show_hostile < level.time && !infront (self, client))
+			{
+				return false;
+			}
+		}
+		else if (r == RANGE_MID)
+		{
+			if (!infront (self, client))
+			{
+				return false;
+			}
+		}
+
+		self->enemy = client;
+
+		if (strcmp(self->enemy->classname, "player_noise") != 0)
+		{
+			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+			if (!self->enemy->client)
+			{
+				self->enemy = self->enemy->enemy;
+				if (!self->enemy->client)
+				{
+					self->enemy = NULL;
+					return false;
+				}
+			}
+		}
+	}
+	else	// heardit
+	{
+		vec3_t	temp;
+
+		if (self->spawnflags & 1)
+		{
+			if (!visible (self, client))
+				return false;
+		}
+		else
+		{
+			if (!gi.inPHS(self->s.origin, client->s.origin))
+				return false;
+		}
+
+		VectorSubtract (client->s.origin, self->s.origin, temp);
+
+		if (VectorLength(temp) > 1000)	// too far to hear
+		{
+			return false;
+		}
+
+		// check area portals - if they are different and not connected then we can't hear it
+		if (client->areanum != self->areanum)
+			if (!gi.AreasConnected(self->areanum, client->areanum))
+				return false;
+
+		self->ideal_yaw = vectoyaw(temp);
+		M_ChangeYaw (self);
+
+		// hunt the sound for a bit; hopefully find the real player
+		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
+		self->enemy = client;
+	}
+
+//
+// got one
+//
+	FoundTarget (self);
+
+	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
+		self->monsterinfo.sight (self, self->enemy);
+
+	return true;
+}
+
+
+//=============================================================================
+
+/*
+============
+FacingIdeal
+
+============
+*/
+qboolean FacingIdeal(edict_t *self)
+{
+	float	delta;
+
+	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
+	if (delta > 45 && delta < 315)
+		return false;
+	return true;
+}
+
+
+//=============================================================================
+
+qboolean M_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	float	chance;
+	trace_t	tr;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+	
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		// don't always melee in easy mode
+		if (skill->value == 0 && (rand()&3) )
+			return false;
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.2;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.1;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.02;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (skill->value == 0)
+		chance *= 0.5;
+	else if (skill->value >= 2)
+		chance *= 2;
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+/*
+=============
+ai_run_melee
+
+Turn and close until within an angle to launch a melee attack
+=============
+*/
+void ai_run_melee(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.melee (self);
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+}
+
+
+/*
+=============
+ai_run_missile
+
+Turn in place until within an angle to launch a missile attack
+=============
+*/
+void ai_run_missile(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.attack (self);
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+}
+
+
+/*
+=============
+ai_run_slide
+
+Strafe sideways, but stay at aproximately the same range
+=============
+*/
+void ai_run_slide(edict_t *self, float distance)
+{
+	float	ofs;
+	
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (self->monsterinfo.lefty)
+		ofs = 90;
+	else
+		ofs = -90;
+	
+	if (M_walkmove (self, self->ideal_yaw + ofs, distance))
+		return;
+		
+	self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+	M_walkmove (self, self->ideal_yaw - ofs, distance);
+}
+
+
+/*
+=============
+ai_checkattack
+
+Decides if we're going to attack or do something else
+used by ai_run and ai_stand
+=============
+*/
+qboolean ai_checkattack (edict_t *self, float /*dist*/)
+{
+	vec3_t		temp;
+	qboolean	hesDeadJim;
+
+// this causes monsters to run blindly to the combat point w/o firing
+	if (self->goalentity)
+	{
+		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+			return false;
+
+		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+		{
+			if ((level.time - self->enemy->teleport_time) > 5.0)
+			{
+				if (self->goalentity == self->enemy)
+				{
+					if (self->movetarget)
+						self->goalentity = self->movetarget;
+					else
+						self->goalentity = NULL;
+				}
+				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			}
+			else
+			{
+				self->show_hostile = level.time + 1;
+				return false;
+			}
+		}
+	}
+
+	enemy_vis = false;
+
+// see if the enemy is dead
+	hesDeadJim = false;
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		hesDeadJim = true;
+	}
+	else if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (self->enemy->health > 0)
+		{
+			hesDeadJim = true;
+			self->monsterinfo.aiflags &= ~AI_MEDIC;
+		}
+	}
+	else
+	{
+		if (self->monsterinfo.aiflags & AI_BRUTAL)
+		{
+			if (self->enemy->health <= -80)
+				hesDeadJim = true;
+		}
+		else
+		{
+			if (self->enemy->health <= 0)
+				hesDeadJim = true;
+		}
+	}
+
+	if (hesDeadJim)
+	{
+		self->enemy = NULL;
+	// FIXME: look all around for other targets
+		if (self->oldenemy && self->oldenemy->health > 0)
+		{
+			self->enemy = self->oldenemy;
+			self->oldenemy = NULL;
+			HuntTarget (self);
+		}
+		else
+		{
+			if (self->movetarget)
+			{
+				self->goalentity = self->movetarget;
+				self->monsterinfo.walk (self);
+			}
+			else
+			{
+				// we need the pausetime otherwise the stand code
+				// will just revert to walking with no target and
+				// the monsters will wonder around aimlessly trying
+				// to hunt the world entity
+				self->monsterinfo.pausetime = level.time + 100000000;
+				self->monsterinfo.stand (self);
+			}
+			return true;
+		}
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+// check knowledge of enemy
+	enemy_vis = visible(self, self->enemy);
+	if (enemy_vis)
+	{
+		self->monsterinfo.search_time = level.time + 5;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+	}
+
+// look for other coop players here
+//	if (coop && self->monsterinfo.search_time < level.time)
+//	{
+//		if (FindTarget (self))
+//			return true;
+//	}
+
+	enemy_infront = infront(self, self->enemy);
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+
+	// JDC self->ideal_yaw = enemy_yaw;
+
+	if (self->monsterinfo.attack_state == AS_MISSILE)
+	{
+		ai_run_missile (self);
+		return true;
+	}
+	if (self->monsterinfo.attack_state == AS_MELEE)
+	{
+		ai_run_melee (self);
+		return true;
+	}
+
+	// if enemy is not currently visible, we will never attack
+	if (!enemy_vis)
+		return false;
+
+	return self->monsterinfo.checkattack (self);
+}
+
+
+/*
+=============
+ai_run
+
+The monster has an enemy it is trying to kill
+=============
+*/
+void ai_run (edict_t *self, float dist)
+{
+	vec3_t		v;
+	edict_t		*tempgoal;
+	edict_t		*save;
+	qboolean	new;
+	edict_t		*marker;
+	float		d1, d2;
+	trace_t		tr;
+	vec3_t		v_forward, v_right;
+	float		left, center, right;
+	vec3_t		left_target, right_target;
+
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+	{
+		M_MoveToGoal (self, dist);
+		return;
+	}
+
+	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+	{
+		VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+		if (VectorLength(v) < 64)
+		{
+			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			self->monsterinfo.stand (self);
+			return;
+		}
+
+		M_MoveToGoal (self, dist);
+
+		if (!FindTarget (self))
+			return;
+	}
+
+	if (ai_checkattack (self, dist))
+		return;
+
+	if (self->monsterinfo.attack_state == AS_SLIDING)
+	{
+		ai_run_slide (self, dist);
+		return;
+	}
+
+	if (enemy_vis)
+	{
+//		if (self.aiflags & AI_LOST_SIGHT)
+//			dprint("regained sight\n");
+		M_MoveToGoal (self, dist);
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+		self->monsterinfo.trail_time = level.time;
+		return;
+	}
+
+	// coop will change to another enemy if visible
+	if (coop->value)
+	{	// FIXME: insane guys get mad with this, which causes crashes!
+		if (FindTarget (self))
+			return;
+	}
+
+	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
+	{
+		M_MoveToGoal (self, dist);
+		self->monsterinfo.search_time = 0;
+//		dprint("search timeout\n");
+		return;
+	}
+
+	save = self->goalentity;
+	tempgoal = G_Spawn();
+	self->goalentity = tempgoal;
+
+	new = false;
+
+	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
+	{
+		// just lost sight of the player, decide where to go first
+//		dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
+		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
+		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+		new = true;
+	}
+
+	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
+	{
+		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
+//		dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
+
+		// give ourself more time since we got this far
+		self->monsterinfo.search_time = level.time + 5;
+
+		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
+		{
+//			dprint("was temp goal; retrying original\n");
+			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
+			marker = NULL;
+			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
+			new = true;
+		}
+		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
+		{
+			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
+			marker = PlayerTrail_PickFirst (self);
+		}
+		else
+		{
+			marker = PlayerTrail_PickNext (self);
+		}
+
+		if (marker)
+		{
+			VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
+			self->monsterinfo.trail_time = marker->timestamp;
+			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
+//			dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
+
+//			debug_drawline(self.origin, self.last_sighting, 52);
+			new = true;
+		}
+	}
+
+	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
+	d1 = VectorLength(v);
+	if (d1 <= dist)
+	{
+		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
+		dist = d1;
+	}
+
+	VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
+
+	if (new)
+	{
+//		gi.dprintf("checking for course correction\n");
+
+		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
+		if (tr.fraction < 1)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			d1 = VectorLength(v);
+			center = tr.fraction;
+			d2 = d1 * ((center+1)/2);
+			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+			AngleVectors(self->s.angles, v_forward, v_right, NULL);
+
+			VectorSet(v, d2, -16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
+			left = tr.fraction;
+
+			VectorSet(v, d2, 16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
+			right = tr.fraction;
+
+			center = (d1*center)/d2;
+			if (left >= center && left > right)
+			{
+				if (left < 1)
+				{
+					VectorSet(v, d2 * left * 0.5, -16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (left_target, self->goalentity->s.origin);
+				VectorCopy (left_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted left\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+			else if (right >= center && right > left)
+			{
+				if (right < 1)
+				{
+					VectorSet(v, d2 * right * 0.5, 16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (right_target, self->goalentity->s.origin);
+				VectorCopy (right_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted right\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+		}
+//		else gi.dprintf("course was fine\n");
+	}
+
+	M_MoveToGoal (self, dist);
+
+	G_FreeEdict(tempgoal);
+
+	if (self)
+		self->goalentity = save;
+}
--- /dev/null
+++ b/crbot/g_chase.c
@@ -1,0 +1,141 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void UpdateChaseCam(edict_t *ent)
+{
+	vec3_t o, ownerv, goal;
+	edict_t *targ;
+	vec3_t forward, right;
+	trace_t trace;
+	int i;
+	vec3_t oldgoal;
+	vec3_t angles;
+
+	// is our chase target gone?
+	if (!ent->client->chase_target->inuse) {
+		ent->client->chase_target = NULL;
+		return;
+	}
+
+	targ = ent->client->chase_target;
+
+	VectorCopy(targ->s.origin, ownerv);
+	VectorCopy(ent->s.origin, oldgoal);
+
+	ownerv[2] += targ->viewheight;
+
+	VectorCopy(targ->client->v_angle, angles);
+	if (angles[PITCH] > 56)
+		angles[PITCH] = 56;
+	AngleVectors (angles, forward, right, NULL);
+	VectorNormalize(forward);
+	VectorMA(ownerv, -30, forward, o);
+
+	if (o[2] < targ->s.origin[2] + 20)
+		o[2] = targ->s.origin[2] + 20;
+
+	// jump animation lifts
+	if (!targ->groundentity)
+		o[2] += 16;
+
+	trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+	VectorCopy(trace.endpos, goal);
+
+	VectorMA(goal, 2, forward, goal);
+
+	// pad for floors and ceilings
+	VectorCopy(goal, o);
+	o[2] += 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] -= 6;
+	}
+
+	VectorCopy(goal, o);
+	o[2] -= 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] += 6;
+	}
+
+	ent->client->ps.pmove.pm_type = PM_FREEZE;
+
+	VectorCopy(goal, ent->s.origin);
+	for (i=0 ; i<3 ; i++)
+		ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
+
+	VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
+	VectorCopy(targ->client->v_angle, ent->client->v_angle);
+
+	ent->viewheight = 0;
+	ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+	gi.linkentity(ent);
+
+	if ((!ent->client->showscores && !ent->client->menu &&
+		!ent->client->showinventory && !ent->client->showhelp &&
+		!(level.framenum & 31)) || ent->client->update_chase) {
+		char s[1024];
+
+		ent->client->update_chase = false;
+		sprintf(s, "xv 0 yb -58 string2 \"Chasing %s\"",
+			targ->client->pers.netname);
+		gi.WriteByte (svc_layout);
+		gi.WriteString (s);
+		gi.unicast(ent, false);
+	}
+
+}
+
+void ChaseNext(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i++;
+		if (i > maxclients->value)
+			i = 1;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (e->solid != SOLID_NOT)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
+
+void ChasePrev(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i--;
+		if (i < 1)
+			i = maxclients->value;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (e->solid != SOLID_NOT)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
--- /dev/null
+++ b/crbot/g_cmds.c
@@ -1,0 +1,1075 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+char *ClientTeam (edict_t *ent)
+{
+	char		*p;
+	static char	value[512];
+
+	value[0] = 0;
+
+	if (!ent->client)
+		return value;
+
+	strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
+	p = strchr(value, '/');
+	if (!p)
+		return value;
+
+	if ((int)(dmflags->value) & DF_MODELTEAMS)
+	{
+		*p = 0;
+		return value;
+	}
+
+	// if ((int)(dmflags->value) & DF_SKINTEAMS)
+	return ++p;
+}
+
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2)
+{
+    if ( ent1->client && ent2->client ) {
+      return ent1->client->pers.team_no>0 && 
+             ent1->client->pers.team_no==ent2->client->pers.team_no;
+      }
+    return false;
+
+/*
+	char	ent1Team [512];
+	char	ent2Team [512];
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		return false;
+
+	strcpy (ent1Team, ClientTeam (ent1));
+	strcpy (ent2Team, ClientTeam (ent2));
+
+	if (strcmp(ent1Team, ent2Team) == 0)
+		return true;
+	return false; */
+}
+
+
+void SelectNextItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+//ZOID
+	if (cl->menu) {
+		PMenu_Next(ent);
+		return;
+	} else if (cl->chase_target) {
+		ChaseNext(ent);
+		return;
+	}
+//ZOID
+    if (cl->menu_no) {
+      cr_menu_down(ent);
+      return;
+      }
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void SelectPrevItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+//ZOID
+	if (cl->menu) {
+		PMenu_Prev(ent);
+		return;
+	} else if (cl->chase_target) {
+		ChasePrev(ent);
+		return;
+	}
+//ZOID
+    if (cl->menu_no) {
+      cr_menu_up(ent);
+      return;
+      }
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void ValidateSelectedItem (edict_t *ent)
+{
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	if (cl->pers.inventory[cl->pers.selected_item])
+		return;		// valid
+
+	SelectNextItem (ent, -1);
+}
+
+
+//=================================================================================
+
+/*
+==================
+Cmd_Give_f
+
+Give items to a client
+==================
+*/
+void Cmd_Give_f (edict_t *ent)
+{
+	char		*name;
+	gitem_t		*it;
+	int			index;
+	int			i;
+	qboolean	give_all;
+	edict_t		*it_ent;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	name = gi.args();
+
+	if (cistrcmp(name, "all") == 0)
+		give_all = true;
+	else
+		give_all = false;
+
+	if (give_all || cistrcmp(gi.argv(1), "health") == 0)
+	{
+		if (gi.argc() == 3)
+			ent->health = atoi(gi.argv(2));
+		else
+			ent->health = ent->max_health;
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "weapons") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_WEAPON))
+				continue;
+			ent->client->pers.inventory[i] += 1;
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "ammo") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_AMMO))
+				continue;
+			Add_Ammo (ent, it, 1000);
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "armor") == 0)
+	{
+		gitem_armor_t	*info;
+
+		it = FindItem("Jacket Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Combat Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Body Armor");
+		info = (gitem_armor_t *)it->info;
+		ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "Power Shield") == 0)
+	{
+		it = FindItem("Power Shield");
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO))
+				continue;
+			ent->client->pers.inventory[i] = 1;
+		}
+		return;
+	}
+
+	it = FindItem (name);
+	if (!it)
+	{
+		name = gi.argv(1);
+		it = FindItem (name);
+		if (!it)
+		{
+			gi.dprintf ("unknown item\n");
+			return;
+		}
+	}
+
+	if (!it->pickup)
+	{
+		gi.dprintf ("non-pickup item\n");
+		return;
+	}
+
+	index = ITEM_INDEX(it);
+
+	if (it->flags & IT_AMMO)
+	{
+		if (gi.argc() == 3)
+			ent->client->pers.inventory[index] = atoi(gi.argv(2));
+		else
+			ent->client->pers.inventory[index] += it->quantity;
+	}
+	else
+	{
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+	}
+}
+
+
+/*
+==================
+Cmd_God_f
+
+Sets client to godmode
+
+argv(0) god
+==================
+*/
+void Cmd_God_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_GODMODE;
+	if (!(ent->flags & FL_GODMODE) )
+		msg = "godmode OFF\n";
+	else
+		msg = "godmode ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Notarget_f
+
+Sets client to notarget
+
+argv(0) notarget
+==================
+*/
+void Cmd_Notarget_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_NOTARGET;
+	if (!(ent->flags & FL_NOTARGET) )
+		msg = "notarget OFF\n";
+	else
+		msg = "notarget ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Noclip_f
+
+argv(0) noclip
+==================
+*/
+void Cmd_Noclip_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+	{
+		ent->movetype = MOVETYPE_WALK;
+		msg = "noclip OFF\n";
+	}
+	else
+	{
+		ent->movetype = MOVETYPE_NOCLIP;
+		msg = "noclip ON\n";
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Use_f
+
+Use an inventory item
+==================
+*/
+void Cmd_Use_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+		return;
+	}
+
+	it->use (ent, it);
+}
+
+
+/*
+==================
+Cmd_Drop_f
+
+Drop an inventory item
+==================
+*/
+void Cmd_Drop_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+//ZOID--special case for tech powerups
+	if (cistrcmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != NULL) {
+		it->drop (ent, it);
+		return;
+	}
+//ZOID
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+		return;
+	}
+
+	it->drop (ent, it);
+}
+
+
+/*
+=================
+Cmd_Inven_f
+=================
+*/
+void Cmd_Inven_f (edict_t *ent)
+{
+	  gclient_t	*cl;
+
+	  cl = ent->client;
+
+	  cl->showscores = false;
+	  cl->showhelp = false;
+
+  //ZOID
+	  if (ent->client->menu) {
+		  PMenu_Close(ent);
+		  ent->client->update_chase = true;
+		  return;
+	  }
+  //ZOID
+
+    if (!bot_menu->value || ent->client->resp.ctf_team==CTF_NOTEAM) {
+	  int			i;
+
+	  if (cl->showinventory)
+	  {
+		  cl->showinventory = false;
+		  return;
+	  }
+
+  //ZOID
+	  if (ctf->value && cl->resp.ctf_team == CTF_NOTEAM) {
+		  CTFOpenJoinMenu(ent);
+		  return;
+	  }
+  //ZOID
+
+	  cl->showinventory = true;
+
+	  gi.WriteByte (svc_inventory);
+	  for (i=0 ; i<MAX_ITEMS ; i++)
+	  {
+		  gi.WriteShort (cl->pers.inventory[i]);
+	  }
+	  gi.unicast (ent, true);
+
+      }
+    else {
+
+      if (cl->menu_no>0) {
+        extern char *dm_statusbar; //in g_spawn.c
+        extern char *single_statusbar;
+
+        if (deathmatch->value) {
+          if (ctf->value) gi.configstring (CS_STATUSBAR, ctf_statusbar);
+                     else gi.configstring (CS_STATUSBAR, dm_statusbar);
+          }
+        else 
+          gi.configstring (CS_STATUSBAR, single_statusbar);
+
+        cl->menu_no=0;
+        cl->menu_item_no=0;
+        return;
+        }
+
+      cl->menu_no=1;
+      cl->menu_item_no=1;
+      cr_menu_init();
+      cr_menu_draw(ent);
+      }
+}
+
+/*
+=================
+Cmd_InvUse_f
+=================
+*/
+void Cmd_InvUse_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+//ZOID
+	if (ent->client->menu) {
+		PMenu_Select(ent);
+		return;
+	}
+//ZOID
+
+    if (ent->client->menu_no) {
+      cr_menu_select(ent,1);
+      return;
+      }
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to use.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+	it->use (ent, it);
+}
+
+//ZOID
+/*
+=================
+Cmd_LastWeap_f
+=================
+*/
+void Cmd_LastWeap_f (edict_t *ent)
+{
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon || !cl->pers.lastweapon)
+		return;
+
+	cl->pers.lastweapon->use (ent, cl->pers.lastweapon);
+}
+//ZOID
+
+/*
+=================
+Cmd_WeapPrev_f
+=================
+*/
+void Cmd_WeapPrev_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (selected_weapon + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		if (cl->pers.weapon == it)
+			return;	// successful
+	}
+}
+
+/*
+=================
+Cmd_WeapNext_f
+=================
+*/
+void Cmd_WeapNext_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		if (cl->pers.weapon == it)
+			return;	// successful
+	}
+}
+
+/*
+=================
+Cmd_WeapLast_f
+=================
+*/
+void Cmd_WeapLast_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon || !cl->pers.lastweapon)
+		return;
+
+	index = ITEM_INDEX(cl->pers.lastweapon);
+	if (!cl->pers.inventory[index])
+		return;
+	it = &itemlist[index];
+	if (!it->use)
+		return;
+	if (! (it->flags & IT_WEAPON) )
+		return;
+	it->use (ent, it);
+}
+
+/*
+=================
+Cmd_InvDrop_f
+=================
+*/
+void Cmd_InvDrop_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+    if (ent->client->menu_no) {
+      cr_menu_select(ent,-1);
+      return;
+      }
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+	it->drop (ent, it);
+}
+
+/*
+=================
+Cmd_Kill_f
+=================
+*/
+void Cmd_Kill_f (edict_t *ent)
+{
+//ZOID
+	if (ent->solid == SOLID_NOT)
+		return;
+//ZOID
+
+	if((level.time - ent->client->respawn_time) < 5)
+		return;
+	ent->flags &= ~FL_GODMODE;
+	ent->health = 0;
+	meansOfDeath = MOD_SUICIDE;
+	player_die (ent, ent, ent, 100000, vec3_origin);
+	// don't even bother waiting for death frames
+	ent->deadflag = DEAD_DEAD;
+	respawn (ent);
+}
+
+/*
+=================
+Cmd_PutAway_f
+=================
+*/
+void Cmd_PutAway_f (edict_t *ent)
+{
+	ent->client->showscores = false;
+	ent->client->showhelp = false;
+	ent->client->showinventory = false;
+//ZOID
+	if (ent->client->menu)
+		PMenu_Close(ent);
+	ent->client->update_chase = true;
+//ZOID
+}
+
+
+int PlayerSort (void const *a, void const *b)
+{
+	int		anum, bnum;
+
+	anum = *(int *)a;
+	bnum = *(int *)b;
+
+	anum = game.clients[anum].ps.stats[STAT_FRAGS];
+	bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
+
+	if (anum < bnum)
+		return -1;
+	if (anum > bnum)
+		return 1;
+	return 0;
+}
+
+/*
+=================
+Cmd_Players_f
+=================
+*/
+void Cmd_Players_f (edict_t *ent)
+{
+	int		i;
+	int		count;
+	char	small[64];
+	char	large[1280];
+	int		index[256];
+
+	count = 0;
+	for (i = 0 ; i < maxclients->value ; i++)
+		if (game.clients[i].pers.connected)
+		{
+			index[count] = i;
+			count++;
+		}
+
+	// sort by frags
+	qsort (index, count, sizeof(index[0]), PlayerSort);
+
+	// print information
+	large[0] = 0;
+
+	for (i = 0 ; i < count ; i++)
+	{
+		Com_sprintf (small, sizeof(small), "%3i %s\n",
+			game.clients[index[i]].ps.stats[STAT_FRAGS],
+			game.clients[index[i]].pers.netname);
+		if (strlen (small) + strlen(large) > sizeof(large) - 100 )
+		{	// can't print all of them in one packet
+			strcat (large, "...\n");
+			break;
+		}
+		strcat (large, small);
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count);
+}
+
+/*
+=================
+Cmd_Wave_f
+=================
+*/
+void Cmd_Wave_f (edict_t *ent)
+{
+	int		i;
+
+	i = atoi (gi.argv(1));
+
+	// can't wave when ducked
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+		return;
+
+	if (ent->client->anim_priority > ANIM_WAVE)
+		return;
+
+	ent->client->anim_priority = ANIM_WAVE;
+
+	switch (i)
+	{
+	case 0:
+		gi.cprintf (ent, PRINT_HIGH, "flipoff\n");
+		ent->s.frame = FRAME_flip01-1;
+		ent->client->anim_end = FRAME_flip12;
+		break;
+	case 1:
+		gi.cprintf (ent, PRINT_HIGH, "salute\n");
+		ent->s.frame = FRAME_salute01-1;
+		ent->client->anim_end = FRAME_salute11;
+		break;
+	case 2:
+		gi.cprintf (ent, PRINT_HIGH, "taunt\n");
+		ent->s.frame = FRAME_taunt01-1;
+		ent->client->anim_end = FRAME_taunt17;
+		break;
+	case 3:
+		gi.cprintf (ent, PRINT_HIGH, "wave\n");
+		ent->s.frame = FRAME_wave01-1;
+		ent->client->anim_end = FRAME_wave11;
+		break;
+	case 4:
+	default:
+		gi.cprintf (ent, PRINT_HIGH, "point\n");
+		ent->s.frame = FRAME_point01-1;
+		ent->client->anim_end = FRAME_point12;
+		break;
+	}
+}
+
+/*
+==================
+Cmd_Say_f
+==================
+*/
+void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0)
+{
+	int		j;
+	edict_t	*other;
+	char	*p;
+	char	text[2048];
+
+	if (gi.argc () < 2 && !arg0)
+		return;
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		team = false;
+
+	if (team)
+		Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname);
+	else
+		Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
+
+	if (arg0)
+	{
+		strcat (text, gi.argv(0));
+		strcat (text, " ");
+		strcat (text, gi.args());
+	}
+	else
+	{
+		p = gi.args();
+
+		if (*p == '"')
+		{
+			p++;
+			p[strlen(p)-1] = 0;
+		}
+		strcat(text, p);
+	}
+
+	// don't let text be too long for malicious reasons
+	if (strlen(text) > 150)
+		text[150] = 0;
+
+	strcat(text, "\n");
+
+	if (dedicated->value)
+		gi.cprintf(NULL, PRINT_CHAT, "%s", text);
+
+	for (j = 1; j <= game.maxclients; j++)
+	{
+		other = &g_edicts[j];
+		if (!other->inuse)
+			continue;
+		if (!other->client)
+			continue;
+		if (team)
+		{
+			if (!OnSameTeam(ent, other))
+				continue;
+		}
+
+        if (!other->bot_info)
+		  gi.cprintf(other, PRINT_CHAT, "%s", text);
+	}
+}
+
+/*
+=================
+ClientCommand
+=================
+*/
+void ClientCommand (edict_t *ent)
+{
+	char	*cmd;
+
+	if (!ent->client)
+		return;		// not fully in game yet
+
+	cmd = gi.argv(0);
+
+	if (cistrcmp (cmd, "players") == 0)
+	{
+		Cmd_Players_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "say") == 0)
+	{
+		Cmd_Say_f (ent, false, false);
+		return;
+	}
+	if (cistrcmp (cmd, "say_team") == 0 || cistrcmp (cmd, "steam") == 0)
+	{
+		CTFSay_Team(ent, gi.args());
+		return;
+	}
+	if (cistrcmp (cmd, "score") == 0)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "help") == 0)
+	{
+		Cmd_Help_f (ent);
+		return;
+	}
+
+	if (level.intermissiontime)
+		return;
+
+	if (cistrcmp (cmd, "use") == 0)
+		Cmd_Use_f (ent);
+	else if (cistrcmp (cmd, "drop") == 0)
+		Cmd_Drop_f (ent);
+	else if (cistrcmp (cmd, "give") == 0)
+		Cmd_Give_f (ent);
+	else if (cistrcmp (cmd, "god") == 0)
+		Cmd_God_f (ent);
+	else if (cistrcmp (cmd, "notarget") == 0)
+		Cmd_Notarget_f (ent);
+	else if (cistrcmp (cmd, "noclip") == 0)
+		Cmd_Noclip_f (ent);
+	else if (cistrcmp (cmd, "inven") == 0)
+		Cmd_Inven_f (ent);
+	else if (cistrcmp (cmd, "invnext") == 0)
+		SelectNextItem (ent, -1);
+	else if (cistrcmp (cmd, "invprev") == 0)
+		SelectPrevItem (ent, -1);
+	else if (cistrcmp (cmd, "invnextw") == 0)
+		SelectNextItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invprevw") == 0)
+		SelectPrevItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invnextp") == 0)
+		SelectNextItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invprevp") == 0)
+		SelectPrevItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invuse") == 0)
+		Cmd_InvUse_f (ent);
+	else if (cistrcmp (cmd, "invdrop") == 0)
+		Cmd_InvDrop_f (ent);
+	else if (cistrcmp (cmd, "weapprev") == 0)
+		Cmd_WeapPrev_f (ent);
+	else if (cistrcmp (cmd, "weapnext") == 0)
+		Cmd_WeapNext_f (ent);
+	else if (cistrcmp (cmd, "weaplast") == 0)
+		Cmd_WeapLast_f (ent);
+	else if (cistrcmp (cmd, "kill") == 0)
+		Cmd_Kill_f (ent);
+	else if (cistrcmp (cmd, "putaway") == 0)
+		Cmd_PutAway_f (ent);
+	else if (cistrcmp (cmd, "wave") == 0)
+		Cmd_Wave_f (ent);
+//ZOID
+	else if (cistrcmp (cmd, "team") == 0)
+	{
+		CTFTeam_f (ent);
+	} else if (cistrcmp(cmd, "id") == 0) {
+		CTFID_f (ent);
+	}
+//ZOID
+    else if ( cistrcmp (cmd, "set_team")==0 || cistrcmp (cmd, "join_team")==0 ) 
+        {
+        ent->client->pers.team_no = atoi(gi.argv(1));
+        }
+    else if (cistrcmp (cmd, "set_pos") == 0) 
+        {
+        ent->s.origin[0] = atof(gi.argv(1));
+        ent->s.origin[1] = atof(gi.argv(2));
+        ent->s.origin[2] = atof(gi.argv(3));
+        }
+    else if (cistrcmp (cmd, "bot_stats") == 0) 
+        {
+        cr_show_stats(ent);
+        }
+    else if (cistrcmp (cmd, "team_stats") == 0) 
+        {
+        cr_show_team_stats(ent);
+        }
+    else if (cistrcmp (cmd, "team_help")  == 0) cr_team_help( ent );
+    else if (cistrcmp (cmd, "team_group") == 0) cr_team_group( ent );
+    else if (cistrcmp (cmd, "team_patrol") == 0) cr_team_patrol( ent, gi.argv(1) );
+    else if (cistrcmp (cmd, "team_guard") == 0) cr_team_guard( ent, gi.argv(1) );
+    else if (cistrcmp (cmd, "team_free") == 0) cr_team_free( ent, gi.argv(1) );
+    else if (cistrcmp (cmd, "save_nodemap") == 0) cr_routes_save(ent);
+	else	// anything that doesn't match a command will be a chat
+		Cmd_Say_f (ent, false, true);
+}
--- /dev/null
+++ b/crbot/g_combat.c
@@ -1,0 +1,581 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+============
+CanDamage
+
+Returns true if the inflictor can directly damage the target.  Used for
+explosions and melee attacks.
+============
+*/
+qboolean CanDamage (edict_t *targ, edict_t *inflictor)
+{
+	vec3_t	dest;
+	trace_t	trace;
+
+// bmodels need special checking because their origin is 0,0,0
+	if (targ->movetype == MOVETYPE_PUSH)
+	{
+		VectorAdd (targ->absmin, targ->absmax, dest);
+		VectorScale (dest, 0.5, dest);
+		trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+		if (trace.fraction == 1.0)
+			return true;
+		if (trace.ent == targ)
+			return true;
+		return false;
+	}
+	
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+
+	return false;
+}
+
+
+/*
+============
+Killed
+============
+*/
+void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+	if (targ->health < -999)
+		targ->health = -999;
+
+	targ->enemy = attacker;
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+//		targ->svflags |= SVF_DEADMONSTER;	// now treat as a different content type
+		if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
+		{
+			level.killed_monsters++;
+			if (coop->value && attacker->client)
+				attacker->client->resp.score++;
+			// medics won't heal monsters that they kill themselves
+			if (strcmp(attacker->classname, "monster_medic") == 0)
+				targ->owner = attacker;
+		}
+	}
+
+	if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
+	{	// doors, triggers, etc
+		targ->die (targ, inflictor, attacker, damage, point);
+		return;
+	}
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+		targ->touch = NULL;
+		monster_death_use (targ);
+	}
+
+	targ->die (targ, inflictor, attacker, damage, point);
+}
+
+
+/*
+================
+SpawnDamage
+================
+*/
+void SpawnDamage (int type, vec3_t origin, vec3_t normal, int /*damage*/)
+{
+	//if (damage > 255)
+	//	damage = 255;
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (type);
+//	gi.WriteByte (damage);
+	gi.WritePosition (origin);
+	gi.WriteDir (normal);
+	gi.multicast (origin, MULTICAST_PVS);
+}
+
+
+/*
+============
+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
+point		point at which the damage is being inflicted
+normal		normal vector from that point
+damage		amount of damage being inflicted
+knockback	force to be applied against targ as a result of the damage
+
+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_ENERGY			damage is from an energy based weapon
+	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
+	DAMAGE_BULLET			damage is from a bullet (used for ricochets)
+	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
+============
+*/
+static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			power_armor_type;
+	int			index = 0;
+	int			damagePerCell;
+	int			pa_te_type;
+	int			power = 0;
+	int			power_used;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (dflags & DAMAGE_NO_ARMOR)
+		return 0;
+
+	if (client)
+	{
+		power_armor_type = PowerArmorType (ent);
+		if (power_armor_type != POWER_ARMOR_NONE)
+		{
+			index = ITEM_INDEX(FindItem("Cells"));
+			power = client->pers.inventory[index];
+		}
+	}
+	else if (ent->svflags & SVF_MONSTER)
+	{
+		power_armor_type = ent->monsterinfo.power_armor_type;
+		power = ent->monsterinfo.power_armor_power;
+	}
+	else
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_NONE)
+		return 0;
+	if (!power)
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_SCREEN)
+	{
+		vec3_t		vec;
+		float		dot;
+		vec3_t		forward;
+
+		// only works if damage point is in front
+		AngleVectors (ent->s.angles, forward, NULL, NULL);
+		VectorSubtract (point, ent->s.origin, vec);
+		VectorNormalize (vec);
+		dot = DotProduct (vec, forward);
+		if (dot <= 0.3)
+			return 0;
+
+		damagePerCell = 1;
+		pa_te_type = TE_SCREEN_SPARKS;
+		damage = damage / 3;
+	}
+	else
+	{
+		damagePerCell = 1; // power armor is weaker in CTF
+		pa_te_type = TE_SHIELD_SPARKS;
+		damage = (2 * damage) / 3;
+	}
+
+	save = power * damagePerCell;
+	if (!save)
+		return 0;
+	if (save > damage)
+		save = damage;
+
+	SpawnDamage (pa_te_type, point, normal, save);
+	ent->powerarmor_time = level.time + 0.2;
+
+	power_used = save / damagePerCell;
+
+	if (client)
+		client->pers.inventory[index] -= power_used;
+	else
+		ent->monsterinfo.power_armor_power -= power_used;
+	return save;
+}
+
+static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			index;
+	gitem_t		*armor;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (!client)
+		return 0;
+
+	if (dflags & DAMAGE_NO_ARMOR)
+		return 0;
+
+	index = ArmorIndex (ent);
+	if (!index)
+		return 0;
+
+	armor = GetItemByIndex (index);
+
+	if (dflags & DAMAGE_ENERGY)
+		save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
+	else
+		save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
+	if (save >= client->pers.inventory[index])
+		save = client->pers.inventory[index];
+
+	if (!save)
+		return 0;
+
+	client->pers.inventory[index] -= save;
+	SpawnDamage (te_sparks, point, normal, save);
+
+	return save;
+}
+
+void M_ReactToDamage (edict_t *targ, edict_t *attacker)
+{
+	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
+		return;
+
+	if (attacker == targ || attacker == targ->enemy)
+		return;
+
+	// if we are a good guy monster and our attacker is a player
+	// or another good guy, do not get mad at them
+	if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
+			return;
+	}
+
+	// we now know that we are not both good guys
+
+	// if attacker is a client, get mad at them because he's good and we're not
+	if (attacker->client)
+	{
+		// this can only happen in coop (both new and old enemies are clients)
+		// only switch if can't see the current enemy
+		if (targ->enemy && targ->enemy->client)
+		{
+			if (visible(targ, targ->enemy))
+			{
+				targ->oldenemy = attacker;
+				return;
+			}
+			targ->oldenemy = targ->enemy;
+		}
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+		return;
+	}
+
+	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
+	// (they spray too much), get mad at them
+	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
+		 (strcmp (targ->classname, attacker->classname) != 0) &&
+		 (strcmp(attacker->classname, "monster_tank") != 0) &&
+		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
+		 (strcmp(attacker->classname, "monster_makron") != 0) &&
+		 (strcmp(attacker->classname, "monster_jorg") != 0) )
+	{
+		if (targ->enemy)
+			if (targ->enemy->client)
+				targ->oldenemy = targ->enemy;
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+	else
+	// otherwise get mad at whoever they are mad at (help our buddy)
+	{
+		if (targ->enemy)
+			if (targ->enemy->client)
+				targ->oldenemy = targ->enemy;
+		targ->enemy = attacker->enemy;
+		FoundTarget (targ);
+	}
+}
+
+qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
+{
+//ZOID
+	if (ctf->value && targ->client && attacker->client)
+		if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
+			targ != attacker)
+			return true;
+//ZOID
+
+		//FIXME make the next line real and uncomment this block
+		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
+	return false;
+}
+
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
+{
+	gclient_t	*client;
+	int			take;
+	int			save;
+	int			asave;
+	int			psave;
+	int			te_sparks;
+
+	if (!targ->takedamage)
+		return;
+
+	// friendly fire avoidance
+	// if enabled you can't hurt teammates (but you can hurt yourself)
+	// knockback still occurs
+	if ( (targ != attacker) && 
+         ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value ||
+          (targ->client && targ->client->pers.team_no>0) ) )
+	{
+		if (OnSameTeam (targ, attacker))
+		{
+			if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
+				damage = 0;
+			else
+				mod |= MOD_FRIENDLY_FIRE;
+		}
+	}
+	meansOfDeath = mod;
+
+	// easy mode takes half damage
+	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
+	{
+		damage *= 0.5;
+		if (!damage)
+			damage = 1;
+	}
+
+	client = targ->client;
+
+	if (dflags & DAMAGE_BULLET)
+		te_sparks = TE_BULLET_SPARKS;
+	else
+		te_sparks = TE_SPARKS;
+
+	VectorNormalize(dir);
+
+// bonus damage for suprising a monster
+	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
+		damage *= 2;
+
+//ZOID
+//strength tech
+	damage = CTFApplyStrength(attacker, damage);
+//ZOID
+
+	if (targ->flags & FL_NO_KNOCKBACK)
+		knockback = 0;
+
+// figure momentum add
+	if (!(dflags & DAMAGE_NO_KNOCKBACK))
+	{
+		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
+		{
+			vec3_t	kvel;
+			float	mass;
+
+			if (targ->mass < 50)
+				mass = 50;
+			else
+				mass = targ->mass;
+
+			if (targ->client  && attacker == targ)
+				VectorScale (dir, 1600.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
+			else
+				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
+
+			VectorAdd (targ->velocity, kvel, targ->velocity);
+		}
+	}
+
+	take = damage;
+	save = 0;
+
+	// check for godmode
+	if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
+	{
+		take = 0;
+		save = damage;
+		SpawnDamage (te_sparks, point, normal, save);
+	}
+
+	// check for invincibility
+	if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
+	{
+		if (targ->pain_debounce_time < level.time)
+		{
+			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
+			targ->pain_debounce_time = level.time + 2;
+		}
+		take = 0;
+		save = damage;
+	}
+
+//ZOID
+//team armor protect
+	if (ctf->value && targ->client && attacker->client &&
+		targ->client->resp.ctf_team == attacker->client->resp.ctf_team &&
+		targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) {
+		psave = asave = 0;
+	} else {
+//ZOID
+		psave = CheckPowerArmor (targ, point, normal, take, dflags);
+		take -= psave;
+	
+		asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
+		take -= asave;
+	}
+
+	//treat cheat/powerup savings the same as armor
+	asave += save;
+
+//ZOID
+//resistance tech
+	take = CTFApplyResistance(targ, take);
+//ZOID
+
+	// team damage avoidance
+	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
+		return;
+
+//ZOID
+	CTFCheckHurtCarrier(targ, attacker);
+//ZOID
+
+// do the damage
+	if (take)
+	{
+		if ((targ->svflags & SVF_MONSTER) || (client))
+			SpawnDamage (TE_BLOOD, point, normal, take);
+		else
+			SpawnDamage (te_sparks, point, normal, take);
+
+
+		targ->health = targ->health - take;
+			
+		if (targ->health <= 0)
+		{
+			if ((targ->svflags & SVF_MONSTER) || (client))
+				targ->flags |= FL_NO_KNOCKBACK;
+			Killed (targ, inflictor, attacker, take, point);
+			return;
+		}
+	}
+
+	if (targ->svflags & SVF_MONSTER)
+	{
+		M_ReactToDamage (targ, attacker);
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
+		{
+			targ->pain (targ, attacker, knockback, take);
+			// nightmare mode monsters don't go into pain frames often
+			if (skill->value == 3)
+				targ->pain_debounce_time = level.time + 5;
+		}
+	}
+	else if (client)
+	{
+		if (!(targ->flags & FL_GODMODE) && (take))
+			targ->pain (targ, attacker, knockback, take);
+	}
+	else if (take)
+	{
+		if (targ->pain)
+			targ->pain (targ, attacker, knockback, take);
+	}
+
+	// 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)
+	{
+		client->damage_parmor += psave;
+		client->damage_armor += asave;
+		client->damage_blood += take;
+		client->damage_knockback += knockback;
+		VectorCopy (point, client->damage_from);
+	}
+}
+
+
+/*
+============
+T_RadiusDamage
+============
+*/
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
+{
+	float	points;
+	edict_t	*ent = NULL;
+	vec3_t	v;
+	vec3_t	dir;
+
+	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+	{
+		if (ent == ignore)
+			continue;
+		if (!ent->takedamage)
+			continue;
+
+		VectorAdd (ent->mins, ent->maxs, v);
+		VectorMA (ent->s.origin, 0.5, v, v);
+		VectorSubtract (inflictor->s.origin, v, v);
+		points = damage - 0.5 * VectorLength (v);
+		if (ent == attacker)
+			points = points * 0.5;
+		if (points > 0)
+		{
+			if (CanDamage (ent, inflictor))
+			{
+				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
+				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+			}
+		}
+	}
+}
--- /dev/null
+++ b/crbot/g_ctf.c
@@ -1,0 +1,2725 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+typedef struct ctfgame_s
+{
+	int team1, team2;
+	int total1, total2; // these are only set when going into intermission!
+	float last_flag_capture;
+	int last_capture_team;
+} ctfgame_t;
+
+ctfgame_t ctfgame;
+qboolean techspawn = false;
+
+cvar_t *ctf;
+cvar_t *ctf_forcejoin;
+
+char *ctf_statusbar =
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+  "xv 246 "
+  "num 2 10 "
+  "xv 296 "
+  "pic 9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+  "xv 148 "
+  "pic 11 "
+"endif "
+
+//  frags
+"xr	-50 "
+"yt 2 "
+"num 3 14 "
+
+//tech
+"yb -129 "
+"if 26 "
+  "xr -26 "
+  "pic 26 "
+"endif "
+
+// red team
+"yb -102 "
+"if 17 "
+  "xr -26 "
+  "pic 17 "
+"endif "
+"xr -62 "
+"num 2 18 "
+//joined overlay
+"if 22 "
+  "yb -104 "
+  "xr -28 "
+  "pic 22 "
+"endif "
+
+// blue team
+"yb -75 "
+"if 19 "
+  "xr -26 "
+  "pic 19 "
+"endif "
+"xr -62 "
+"num 2 20 "
+"if 23 "
+  "yb -77 "
+  "xr -28 "
+  "pic 23 "
+"endif "
+
+// have flag graph
+"if 21 "
+  "yt 26 "
+  "xr -24 "
+  "pic 21 "
+"endif "
+
+// id view state
+"if 27 "
+  "xv 0 "
+  "yb -58 "
+  "string \"Viewing\" "
+  "xv 64 "
+  "stat_string 27 "
+"endif "
+;
+
+static char *tnames[] = {
+	"item_tech1", "item_tech2", "item_tech3", "item_tech4",
+	NULL
+};
+
+void stuffcmd(edict_t *ent, char *s) 	
+{
+   	gi.WriteByte (11);	        
+	gi.WriteString (s);
+    gi.unicast (ent, true);	
+}
+
+/*--------------------------------------------------------------------------*/
+
+/*
+=================
+findradius
+
+Returns entities that have origins within a spherical area
+
+findradius (origin, radius)
+=================
+*/
+static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad)
+{
+	vec3_t	eorg;
+	int		j;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (!from->inuse)
+			continue;
+/*
+		if (from->solid == SOLID_NOT)
+			continue;
+*/
+		for (j=0 ; j<3 ; j++)
+			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
+		if (VectorLength(eorg) > rad)
+			continue;
+		return from;
+	}
+
+	return NULL;
+}
+
+static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
+{
+	VectorAdd(org, mins, p[0]);
+	VectorCopy(p[0], p[1]);
+	p[1][0] -= mins[0];
+	VectorCopy(p[0], p[2]);
+	p[2][1] -= mins[1];
+	VectorCopy(p[0], p[3]);
+	p[3][0] -= mins[0];
+	p[3][1] -= mins[1];
+	VectorAdd(org, maxs, p[4]);
+	VectorCopy(p[4], p[5]);
+	p[5][0] -= maxs[0];
+	VectorCopy(p[0], p[6]);
+	p[6][1] -= maxs[1];
+	VectorCopy(p[0], p[7]);
+	p[7][0] -= maxs[0];
+	p[7][1] -= maxs[1];
+}
+
+static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
+{
+	trace_t	trace;
+	vec3_t	targpoints[8];
+	int i;
+	vec3_t viewpoint;
+
+// bmodels need special checking because their origin is 0,0,0
+	if (targ->movetype == MOVETYPE_PUSH)
+		return false; // bmodels not supported
+
+	loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
+	
+	VectorCopy(inflictor->s.origin, viewpoint);
+	viewpoint[2] += inflictor->viewheight;
+
+	for (i = 0; i < 8; i++) {
+		trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
+		if (trace.fraction == 1.0)
+			return true;
+	}
+
+	return false;
+}
+
+/*--------------------------------------------------------------------------*/
+
+static gitem_t *flag1_item;
+static gitem_t *flag2_item;
+
+void CTFInit(void)
+{
+	ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO);
+	ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0);
+
+	if (!flag1_item)
+		flag1_item = FindItemByClassname("item_flag_team1");
+	if (!flag2_item)
+		flag2_item = FindItemByClassname("item_flag_team2");
+	memset(&ctfgame, 0, sizeof(ctfgame));
+	techspawn = false;
+}
+
+/*--------------------------------------------------------------------------*/
+
+char *CTFTeamName(int team)
+{
+	switch (team) {
+	case CTF_TEAM1:
+		return "RED";
+	case CTF_TEAM2:
+		return "BLUE";
+	}
+	return "UKNOWN";
+}
+
+char *CTFOtherTeamName(int team)
+{
+	switch (team) {
+	case CTF_TEAM1:
+		return "BLUE";
+	case CTF_TEAM2:
+		return "RED";
+	}
+	return "UKNOWN";
+}
+
+int CTFOtherTeam(int team)
+{
+	switch (team) {
+	case CTF_TEAM1:
+		return CTF_TEAM2;
+	case CTF_TEAM2:
+		return CTF_TEAM1;
+	}
+	return -1; // invalid value
+}
+
+/*--------------------------------------------------------------------------*/
+
+edict_t *SelectRandomDeathmatchSpawnPoint (void);
+edict_t *SelectFarthestDeathmatchSpawnPoint (void);
+float	PlayersRangeFromSpot (edict_t *spot);
+
+void CTFAssignSkin(edict_t *ent, char *s)
+{
+	int playernum = ent-g_edicts-1;
+	char *p;
+	char t[64];
+
+	Com_sprintf(t, sizeof(t), "%s", s);
+
+	if ((p = strrchr(t, '/')) != NULL)
+		p[1] = 0;
+	else
+		strcpy(t, "male/");
+
+	switch (ent->client->resp.ctf_team) {
+	case CTF_TEAM1:
+		gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", 
+			ent->client->pers.netname, t, CTF_TEAM1_SKIN) );
+		break;
+	case CTF_TEAM2:
+		gi.configstring (CS_PLAYERSKINS+playernum,
+			va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) );
+		break;
+	default:
+		gi.configstring (CS_PLAYERSKINS+playernum, 
+			va("%s\\%s", ent->client->pers.netname, s) );
+		break;
+	}
+//	gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname);
+}
+
+void CTFAssignTeam(gclient_t *who)
+{
+	edict_t		*player;
+	int i;
+	int team1count = 0, team2count = 0;
+
+	who->resp.ctf_state = CTF_STATE_START;
+
+	if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) {
+		who->resp.ctf_team = CTF_NOTEAM;
+		return;
+	}
+
+	for (i = 1; i <= maxclients->value; i++) {
+		player = &g_edicts[i];
+
+		if (!player->inuse || player->client == who)
+			continue;
+
+		switch (player->client->resp.ctf_team) {
+		case CTF_TEAM1:
+			team1count++;
+			break;
+		case CTF_TEAM2:
+			team2count++;
+		}
+	}
+	if (team1count < team1count)
+		who->resp.ctf_team = CTF_TEAM1;
+	else if (team2count < team1count)
+		who->resp.ctf_team = CTF_TEAM2;
+	else if (rand() & 1)
+		who->resp.ctf_team = CTF_TEAM1;
+	else
+		who->resp.ctf_team = CTF_TEAM2;
+}
+
+/*
+================
+SelectCTFSpawnPoint
+
+go to a ctf point, but NOT the two points closest
+to other players
+================
+*/
+edict_t *SelectCTFSpawnPoint (edict_t *ent)
+{
+	edict_t	*spot, *spot1, *spot2;
+	int		count = 0;
+	int		selection;
+	float	range, range1, range2;
+	char	*cname;
+
+	if (ent->client->resp.ctf_state != CTF_STATE_START) {
+		if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
+			return SelectFarthestDeathmatchSpawnPoint ();
+		else
+			return SelectRandomDeathmatchSpawnPoint ();
+	}
+
+	ent->client->resp.ctf_state = CTF_STATE_PLAYING;
+
+	switch (ent->client->resp.ctf_team) {
+	case CTF_TEAM1:
+		cname = "info_player_team1";
+		break;
+	case CTF_TEAM2:
+		cname = "info_player_team2";
+		break;
+	default:
+		return SelectRandomDeathmatchSpawnPoint();
+	}
+
+	spot = NULL;
+	range1 = range2 = 99999;
+	spot1 = spot2 = NULL;
+
+	while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
+	{
+		count++;
+		range = PlayersRangeFromSpot(spot);
+		if (range < range1)
+		{
+			range1 = range;
+			spot1 = spot;
+		}
+		else if (range < range2)
+		{
+			range2 = range;
+			spot2 = spot;
+		}
+	}
+
+	if (!count)
+		return SelectRandomDeathmatchSpawnPoint();
+
+	if (count <= 2)
+	{
+		spot1 = spot2 = NULL;
+	}
+	else
+		count -= 2;
+
+	selection = rand() % count;
+
+	spot = NULL;
+	do
+	{
+		spot = G_Find (spot, FOFS(classname), cname);
+		if (spot == spot1 || spot == spot2)
+			selection++;
+	} while(selection--);
+
+	return spot;
+}
+
+/*------------------------------------------------------------------------*/
+/*
+CTFFragBonuses
+
+Calculate the bonuses for flag defense, flag carrier defense, etc.
+Note that bonuses are not cumaltive.  You get one, they are in importance
+order.
+*/
+void CTFFragBonuses(edict_t *targ, edict_t */*inflictor*/, edict_t *attacker)
+{
+	int i;
+	edict_t *ent;
+	gitem_t *flag_item, *enemy_flag_item;
+	int otherteam;
+	edict_t *flag, *carrier = nil;
+	char *c;
+	vec3_t v1, v2;
+
+	// no bonus for fragging yourself
+	if (!targ->client || !attacker->client || targ == attacker)
+		return;
+
+	otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
+	if (otherteam < 0)
+		return; // whoever died isn't on a team
+
+	// same team, if the flag at base, check to he has the enemy flag
+	if (targ->client->resp.ctf_team == CTF_TEAM1) {
+		flag_item = flag1_item;
+		enemy_flag_item = flag2_item;
+	} else {
+		flag_item = flag2_item;
+		enemy_flag_item = flag1_item;
+	}
+
+	// did the attacker frag the flag carrier?
+	if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
+		attacker->client->resp.ctf_lastfraggedcarrier = level.time;
+		attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
+        if (!attacker->bot_info) 
+		  gi.cprintf( attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n",
+			          CTF_FRAG_CARRIER_BONUS);
+
+		// the the target had the flag, clear the hurt carrier
+		// field on the other team
+		for (i = 1; i <= maxclients->value; i++) {
+			ent = g_edicts + i;
+			if (ent->inuse && ent->client->resp.ctf_team == otherteam)
+				ent->client->resp.ctf_lasthurtcarrier = 0;
+		}
+		return;
+	}
+
+	if (targ->client->resp.ctf_lasthurtcarrier &&
+		level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
+		!attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) {
+		// attacker is on the same team as the flag carrier and
+		// fragged a guy who hurt our flag carrier
+		attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
+		gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n",
+			attacker->client->pers.netname, 
+			CTFTeamName(attacker->client->resp.ctf_team));
+		return;
+	}
+
+	// flag and flag carrier area defense bonuses
+
+	// we have to find the flag and carrier entities
+
+	// find the flag
+	switch (attacker->client->resp.ctf_team) {
+	case CTF_TEAM1:
+		c = "item_flag_team1";
+		break;
+	case CTF_TEAM2:
+		c = "item_flag_team2";
+		break;
+	default:
+		return;
+	}
+
+	flag = NULL;
+	while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) {
+		if (!(flag->spawnflags & DROPPED_ITEM))
+			break;
+	}
+
+	if (!flag)
+		return; // can't find attacker's flag
+
+	// find attacker's team's flag carrier
+	for (i = 1; i <= maxclients->value; i++) {
+		carrier = g_edicts + i;
+		if (carrier->inuse && 
+			carrier->client->pers.inventory[ITEM_INDEX(flag_item)])
+			break;
+		carrier = NULL;
+	}
+
+	// ok we have the attackers flag and a pointer to the carrier
+
+	// check to see if we are defending the base's flag
+	VectorSubtract(targ->s.origin, flag->s.origin, v1);
+	VectorSubtract(attacker->s.origin, flag->s.origin, v2);
+
+	if (VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS ||
+		VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS ||
+		loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) {
+		// we defended the base flag
+		attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
+		if (flag->solid == SOLID_NOT)
+			gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n",
+				attacker->client->pers.netname, 
+				CTFTeamName(attacker->client->resp.ctf_team));
+		else
+			gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n",
+				attacker->client->pers.netname, 
+				CTFTeamName(attacker->client->resp.ctf_team));
+		return;
+	}
+
+	if (carrier && carrier != attacker) {
+		VectorSubtract(targ->s.origin, carrier->s.origin, v1);
+		VectorSubtract(attacker->s.origin, carrier->s.origin, v1);
+
+		if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS ||
+			VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS ||
+			loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) {
+			attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
+			gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n",
+				attacker->client->pers.netname, 
+				CTFTeamName(attacker->client->resp.ctf_team));
+			return;
+		}
+	}
+}
+
+void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
+{
+	gitem_t *flag_item;
+
+	if (!targ->client || !attacker->client)
+		return;
+
+	if (targ->client->resp.ctf_team == CTF_TEAM1)
+		flag_item = flag2_item;
+	else
+		flag_item = flag1_item;
+
+	if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] &&
+		targ->client->resp.ctf_team != attacker->client->resp.ctf_team)
+		attacker->client->resp.ctf_lasthurtcarrier = level.time;
+}
+
+
+/*------------------------------------------------------------------------*/
+
+void CTFResetFlag(int ctf_team)
+{
+	char *c;
+	edict_t *ent;
+
+	switch (ctf_team) {
+	case CTF_TEAM1:
+		c = "item_flag_team1";
+		break;
+	case CTF_TEAM2:
+		c = "item_flag_team2";
+		break;
+	default:
+		return;
+	}
+
+	ent = NULL;
+	while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) {
+		if (ent->spawnflags & DROPPED_ITEM)
+			G_FreeEdict(ent);
+		else {
+			ent->svflags &= ~SVF_NOCLIENT;
+			ent->solid = SOLID_TRIGGER;
+			gi.linkentity(ent);
+			ent->s.event = EV_ITEM_RESPAWN;
+		}
+	}
+}
+
+void CTFResetFlags(void)
+{
+	CTFResetFlag(CTF_TEAM1);
+	CTFResetFlag(CTF_TEAM2);
+}
+
+qboolean CTFPickup_Flag(edict_t *ent, edict_t *other)
+{
+	int ctf_team;
+	int i;
+	edict_t *player;
+	gitem_t *flag_item, *enemy_flag_item;
+
+	// figure out what team this flag is
+	if (strcmp(ent->classname, "item_flag_team1") == 0)
+		ctf_team = CTF_TEAM1;
+	else if (strcmp(ent->classname, "item_flag_team2") == 0)
+		ctf_team = CTF_TEAM2;
+	else {
+        if (!ent->bot_info) 
+		  gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
+		return false;
+	}
+
+	// same team, if the flag at base, check to he has the enemy flag
+	if (ctf_team == CTF_TEAM1) {
+		flag_item = flag1_item;
+		enemy_flag_item = flag2_item;
+	} else {
+		flag_item = flag2_item;
+		enemy_flag_item = flag1_item;
+	}
+
+	if (ctf_team == other->client->resp.ctf_team) {
+
+		if (!(ent->spawnflags & DROPPED_ITEM)) {
+			// the flag is at home base.  if the player has the enemy
+			// flag, he's just won!
+		
+			if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
+				gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n",
+						other->client->pers.netname, CTFOtherTeamName(ctf_team));
+				other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0;
+
+				ctfgame.last_flag_capture = level.time;
+				ctfgame.last_capture_team = ctf_team;
+				if (ctf_team == CTF_TEAM1)
+					ctfgame.team1++;
+				else
+					ctfgame.team2++;
+
+				gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
+
+				// other gets another 10 frag bonus
+				other->client->resp.score += CTF_CAPTURE_BONUS;
+
+				// Ok, let's do the player loop, hand out the bonuses
+				for (i = 1; i <= maxclients->value; i++) {
+					player = &g_edicts[i];
+					if (!player->inuse)
+						continue;
+
+					if (player->client->resp.ctf_team != other->client->resp.ctf_team)
+						player->client->resp.ctf_lasthurtcarrier = -5;
+					else if (player->client->resp.ctf_team == other->client->resp.ctf_team) {
+						if (player != other)
+							player->client->resp.score += CTF_TEAM_BONUS;
+						// award extra points for capture assists
+						if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) {
+							gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname);
+							player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
+						}
+						if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) {
+							gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname);
+							player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
+						}
+					}
+				}
+
+				CTFResetFlags();
+				return false;
+			}
+			return false; // its at home base already
+		}	
+		// hey, its not home.  return it by teleporting it back
+		gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", 
+			other->client->pers.netname, CTFTeamName(ctf_team));
+		other->client->resp.score += CTF_RECOVERY_BONUS;
+		other->client->resp.ctf_lastreturnedflag = level.time;
+		gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
+		//CTFResetFlag will remove this entity!  We must return false
+		CTFResetFlag(ctf_team);
+		return false;
+	}
+
+	// hey, its not our flag, pick it up
+	gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n",
+		other->client->pers.netname, CTFTeamName(ctf_team));
+	other->client->resp.score += CTF_FLAG_BONUS;
+
+	other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1;
+	other->client->resp.ctf_flagsince = level.time;
+
+	// pick up the flag
+	// if it's not a dropped flag, we just make is disappear
+	// if it's dropped, it will be removed by the pickup caller
+	if (!(ent->spawnflags & DROPPED_ITEM)) {
+		ent->flags |= FL_RESPAWN;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+	}
+	return true;
+}
+
+static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	//owner (who dropped us) can't touch for two secs
+	if (other == ent->owner && 
+		ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2)
+		return;
+
+	Touch_Item (ent, other, plane, surf);
+}
+
+static void CTFDropFlagThink(edict_t *ent)
+{
+	// auto return the flag
+	// reset flag will remove ourselves
+	if (strcmp(ent->classname, "item_flag_team1") == 0) {
+		CTFResetFlag(CTF_TEAM1);
+		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
+			CTFTeamName(CTF_TEAM1));
+	} else if (strcmp(ent->classname, "item_flag_team2") == 0) {
+		CTFResetFlag(CTF_TEAM2);
+		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
+			CTFTeamName(CTF_TEAM2));
+	}
+}
+
+// Called from PlayerDie, to drop the flag from a dying player
+void CTFDeadDropFlag(edict_t *self)
+{
+	edict_t *dropped = NULL;
+
+	if (!flag1_item || !flag2_item)
+		CTFInit();
+
+	if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
+		dropped = Drop_Item(self, flag1_item);
+		self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0;
+		gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
+			self->client->pers.netname, CTFTeamName(CTF_TEAM1));
+	} else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
+		dropped = Drop_Item(self, flag2_item);
+		self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0;
+		gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
+			self->client->pers.netname, CTFTeamName(CTF_TEAM2));
+	}
+
+	if (dropped) {
+		dropped->think = CTFDropFlagThink;
+		dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
+		dropped->touch = CTFDropFlagTouch;
+	}
+}
+
+void CTFDrop_Flag(edict_t *ent, gitem_t *)
+{
+    if (!ent->bot_info) {
+	  if (rand() & 1) 
+		  gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n");
+	  else
+		  gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n");
+      }
+}
+
+static void CTFFlagThink(edict_t *ent)
+{
+	if (ent->solid != SOLID_NOT)
+		ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+
+void CTFFlagSetup (edict_t *ent)
+{
+	trace_t		tr;
+	vec3_t		dest;
+	float		*v;
+
+	v = tv(-15,-15,-15);
+	VectorCopy (v, ent->mins);
+	v = tv(15,15,15);
+	VectorCopy (v, ent->maxs);
+
+	if (ent->model)
+		gi.setmodel (ent, ent->model);
+	else
+		gi.setmodel (ent, ent->item->world_model);
+	ent->solid = SOLID_TRIGGER;
+	ent->movetype = MOVETYPE_TOSS;  
+	ent->touch = Touch_Item;
+
+	v = tv(0,0,-128);
+	VectorAdd (ent->s.origin, v, dest);
+
+	tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+	if (tr.startsolid)
+	{
+		gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	VectorCopy (tr.endpos, ent->s.origin);
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	ent->think = CTFFlagThink;
+}
+
+void CTFEffects(edict_t *player)
+{
+	player->s.effects &= (EF_FLAG1 | EF_FLAG2);
+	if (player->health > 0) {
+		if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
+			player->s.effects |= EF_FLAG1;
+		}
+		if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
+			player->s.effects |= EF_FLAG2;
+		}
+	}
+
+	if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
+		player->s.modelindex3 = gi.modelindex("players/male/flag1.md2");
+	else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
+		player->s.modelindex3 = gi.modelindex("players/male/flag2.md2");
+	else
+		player->s.modelindex3 = 0;
+}
+
+// called when we enter the intermission
+void CTFCalcScores(void)
+{
+	int i;
+
+	ctfgame.total1 = ctfgame.total2 = 0;
+	for (i = 0; i < maxclients->value; i++) {
+		if (!g_edicts[i+1].inuse)
+			continue;
+		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
+			ctfgame.total1 += game.clients[i].resp.score;
+		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
+			ctfgame.total2 += game.clients[i].resp.score;
+	}
+}
+
+void CTFID_f (edict_t *ent)
+{
+	if (ent->client->resp.id_state) {
+		gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n");
+		ent->client->resp.id_state = false;
+	} else {
+		gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n");
+		ent->client->resp.id_state = true;
+	}
+}
+
+static void CTFSetIDView(edict_t *ent)
+{
+	vec3_t	forward, dir;
+	trace_t	tr;
+	edict_t	*who, *best;
+	float	bd = 0, d;
+	int i;
+
+	ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
+
+	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
+	VectorScale(forward, 1024, forward);
+	VectorAdd(ent->s.origin, forward, forward);
+	tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
+	if (tr.fraction < 1 && tr.ent && tr.ent->client) {
+		ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
+			CS_PLAYERSKINS + (ent - g_edicts - 1);
+		return;
+	}
+
+	AngleVectors(ent->client->v_angle, forward, NULL, NULL);
+	best = NULL;
+	for (i = 1; i <= maxclients->value; i++) {
+		who = g_edicts + i;
+		if (!who->inuse)
+			continue;
+		VectorSubtract(who->s.origin, ent->s.origin, dir);
+		VectorNormalize(dir);
+		d = DotProduct(forward, dir);
+		if (d > bd && loc_CanSee(ent, who)) {
+			bd = d;
+			best = who;
+		}
+	}
+	if (bd > 0.90)
+		ent->client->ps.stats[STAT_CTF_ID_VIEW] = 
+			CS_PLAYERSKINS + (best - g_edicts - 1);
+}
+
+void SetCTFStats(edict_t *ent)
+{
+	gitem_t *tech;
+	int i;
+	int p1, p2;
+	edict_t *e;
+
+	// logo headers for the frag display
+	ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = gi.imageindex ("ctfsb1");
+	ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = gi.imageindex ("ctfsb2");
+
+	// if during intermission, we must blink the team header of the winning team
+	if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
+		// note that ctfgame.total[12] is set when we go to intermission
+		if (ctfgame.team1 > ctfgame.team2)
+			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
+		else if (ctfgame.team2 > ctfgame.team1)
+			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
+		else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
+			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
+		else if (ctfgame.total2 > ctfgame.total1) 
+			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
+		else { // tie game!
+			ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
+			ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
+		}
+	}
+
+	// tech icon
+	i = 0;
+	ent->client->ps.stats[STAT_CTF_TECH] = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+			ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon);
+			break;
+		}
+		i++;
+	}
+
+	// figure out what icon to display for team logos
+	// three states:
+	//   flag at base
+	//   flag taken
+	//   flag dropped
+	p1 = gi.imageindex ("i_ctf1");
+	e = G_Find(NULL, FOFS(classname), "item_flag_team1");
+	if (e != NULL) {
+		if (e->solid == SOLID_NOT) {
+			int i;
+
+			// not at base
+			// check if on player
+			p1 = gi.imageindex ("i_ctf1d"); // default to dropped
+			for (i = 1; i <= maxclients->value; i++)
+				if (g_edicts[i].inuse &&
+					g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) {
+					// enemy has it
+					p1 = gi.imageindex ("i_ctf1t");
+					break;
+				}
+		} else if (e->spawnflags & DROPPED_ITEM)
+			p1 = gi.imageindex ("i_ctf1d"); // must be dropped
+	}
+	p2 = gi.imageindex ("i_ctf2");
+	e = G_Find(NULL, FOFS(classname), "item_flag_team2");
+	if (e != NULL) {
+		if (e->solid == SOLID_NOT) {
+			int i;
+
+			// not at base
+			// check if on player
+			p2 = gi.imageindex ("i_ctf2d"); // default to dropped
+			for (i = 1; i <= maxclients->value; i++)
+				if (g_edicts[i].inuse &&
+					g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) {
+					// enemy has it
+					p2 = gi.imageindex ("i_ctf2t");
+					break;
+				}
+		} else if (e->spawnflags & DROPPED_ITEM)
+			p2 = gi.imageindex ("i_ctf2d"); // must be dropped
+	}
+
+
+	ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
+	ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
+
+	if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) {
+		if (ctfgame.last_capture_team == CTF_TEAM1)
+			if (level.framenum & 8)
+				ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
+			else
+				ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
+		else
+			if (level.framenum & 8)
+				ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
+			else
+				ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
+	}
+
+	ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1;
+	ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2;
+
+	ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
+	if (ent->client->resp.ctf_team == CTF_TEAM1 &&
+		ent->client->pers.inventory[ITEM_INDEX(flag2_item)] &&
+		(level.framenum & 8))
+		ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf2");
+
+	else if (ent->client->resp.ctf_team == CTF_TEAM2 &&
+		ent->client->pers.inventory[ITEM_INDEX(flag1_item)] &&
+		(level.framenum & 8))
+		ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf1");
+
+	ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
+	ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
+	if (ent->client->resp.ctf_team == CTF_TEAM1)
+		ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = gi.imageindex ("i_ctfj");
+	else if (ent->client->resp.ctf_team == CTF_TEAM2)
+		ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = gi.imageindex ("i_ctfj");
+
+	ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
+	if (ent->client->resp.id_state)
+		CTFSetIDView(ent);
+}
+
+/*------------------------------------------------------------------------*/
+
+/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
+potential team1 spawning position for ctf games
+*/
+void SP_info_player_team1(edict_t *)
+{
+}
+
+/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
+potential team2 spawning position for ctf games
+*/
+void SP_info_player_team2(edict_t *)
+{
+}
+
+
+/*------------------------------------------------------------------------*/
+/* GRAPPLE																  */
+/*------------------------------------------------------------------------*/
+
+// ent is player
+void CTFPlayerResetGrapple(edict_t *ent)
+{
+	if (ent->client && ent->client->ctf_grapple)
+		CTFResetGrapple(ent->client->ctf_grapple);
+}
+
+// self is grapple, not player
+void CTFResetGrapple(edict_t *self)
+{
+	if (self->owner->client->ctf_grapple) {
+		float volume = 1.0;
+		gclient_t *cl;
+
+		if (self->owner->client->silencer_shots)
+			volume = 0.2;
+
+		gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0);
+		cl = self->owner->client;
+		cl->ctf_grapple = NULL;
+		cl->ctf_grapplereleasetime = level.time;
+		cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
+		cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+		G_FreeEdict(self);
+	}
+}
+
+void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	float volume = 1.0;
+
+	if (other == self->owner)
+		return;
+
+	if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		CTFResetGrapple(self);
+		return;
+	}
+
+	VectorCopy(vec3_origin, self->velocity);
+
+	PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage) {
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE);
+		CTFResetGrapple(self);
+		return;
+	}
+
+	self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
+	self->enemy = other;
+
+	self->solid = SOLID_NOT;
+
+	if (self->owner->client->silencer_shots)
+		volume = 0.2;
+
+	gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0);
+	gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPARKS);
+	gi.WritePosition (self->s.origin);
+	if (!plane)
+		gi.WriteDir (vec3_origin);
+	else
+		gi.WriteDir (plane->normal);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+// draw beam between grapple and self
+void CTFGrappleDrawCable(edict_t *self)
+{
+	vec3_t	offset, start, end, f, r;
+	vec3_t	dir;
+	float	distance;
+
+	AngleVectors (self->owner->client->v_angle, f, r, NULL);
+	VectorSet(offset, 16, 16, self->owner->viewheight-8);
+	P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start);
+
+	VectorSubtract(start, self->owner->s.origin, offset);
+
+	VectorSubtract (start, self->s.origin, dir);
+	distance = VectorLength(dir);
+	// don't draw cable if close
+	if (distance < 64)
+		return;
+
+/*
+	if (distance > 256)
+		return;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 45)
+		return;
+
+	trace_t	tr; //!!
+
+	tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT);
+	if (tr.ent != self) {
+		CTFResetGrapple(self);
+		return;
+	}
+*/
+
+	// adjust start for beam origin being in middle of a segment
+//	VectorMA (start, 8, f, start);
+
+	VectorCopy (self->s.origin, end);
+	// adjust end z for end spot since the monster is currently dead
+//	end[2] = self->absmin[2] + self->size[2] / 2;
+
+	gi.WriteByte (svc_temp_entity);
+/* ifdef USE_GRAPPLE_CABLE */
+	gi.WriteByte (TE_GRAPPLE_CABLE);
+	gi.WriteShort (self->owner - g_edicts);
+	gi.WritePosition (self->owner->s.origin);
+	gi.WritePosition (end);
+	gi.WritePosition (offset);
+/*
+	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (end);
+	gi.WritePosition (start);
+*/
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+void SV_AddGravity (edict_t *ent);
+
+// pull the player toward the grapple
+void CTFGrapplePull(edict_t *self)
+{
+	vec3_t hookdir, v;
+	float vlen;
+
+	if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 &&
+		!self->owner->client->newweapon &&
+		self->owner->client->weaponstate != WEAPON_FIRING &&
+		self->owner->client->weaponstate != WEAPON_ACTIVATING) {
+		CTFResetGrapple(self);
+		return;
+	}
+
+	if (self->enemy) {
+		if (self->enemy->solid == SOLID_NOT) {
+			CTFResetGrapple(self);
+			return;
+		}
+		if (self->enemy->solid == SOLID_BBOX) {
+			VectorScale(self->enemy->size, 0.5, v);
+			VectorAdd(v, self->enemy->s.origin, v);
+			VectorAdd(v, self->enemy->mins, self->s.origin);
+			gi.linkentity (self);
+		} else
+			VectorCopy(self->enemy->velocity, self->velocity);
+		if (self->enemy->takedamage &&
+			!CheckTeamDamage (self->enemy, self->owner)) {
+			float volume = 1.0;
+
+			if (self->owner->client->silencer_shots)
+				volume = 0.2;
+
+			T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE);
+			gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0);
+		}
+		if (self->enemy->deadflag) { // he died
+			CTFResetGrapple(self);
+			return;
+		}
+	}
+
+	CTFGrappleDrawCable(self);
+
+	if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
+		// pull player toward grapple
+		// this causes icky stuff with prediction, we need to extend
+		// the prediction layer to include two new fields in the player
+		// move stuff: a point and a velocity.  The client should add
+		// that velociy in the direction of the point
+		vec3_t forward, up;
+
+		AngleVectors (self->owner->client->v_angle, forward, NULL, up);
+		VectorCopy(self->owner->s.origin, v);
+		v[2] += self->owner->viewheight;
+		VectorSubtract (self->s.origin, v, hookdir);
+
+		vlen = VectorLength(hookdir);
+
+		if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL &&
+			vlen < 64) {
+			float volume = 1.0;
+
+			if (self->owner->client->silencer_shots)
+				volume = 0.2;
+
+			self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+			gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0);
+			self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
+		}
+
+		VectorNormalize (hookdir);
+		VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir);
+		VectorCopy(hookdir, self->owner->velocity);
+		SV_AddGravity(self->owner);
+	}
+}
+
+void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
+{
+	edict_t	*grapple;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	grapple = G_Spawn();
+	VectorCopy (start, grapple->s.origin);
+	VectorCopy (start, grapple->s.old_origin);
+	vectoangles (dir, grapple->s.angles);
+	VectorScale (dir, speed, grapple->velocity);
+	grapple->movetype = MOVETYPE_FLYMISSILE;
+	grapple->clipmask = MASK_SHOT;
+	grapple->solid = SOLID_BBOX;
+	grapple->s.effects |= effect;
+	VectorClear (grapple->mins);
+	VectorClear (grapple->maxs);
+	grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2");
+//	grapple->s.sound = gi.soundindex ("misc/lasfly.wav");
+	grapple->owner = self;
+	grapple->touch = CTFGrappleTouch;
+//	grapple->nextthink = level.time + FRAMETIME;
+//	grapple->think = CTFGrappleThink;
+	grapple->dmg = damage;
+	self->client->ctf_grapple = grapple;
+	self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
+	gi.linkentity (grapple);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (grapple->s.origin, -10, dir, grapple->s.origin);
+		grapple->touch (grapple, tr.ent, NULL, NULL);
+	}
+}	
+
+void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	offset;
+	float volume = 1.0;
+
+	if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
+		return; // it's already out
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+//	VectorSet(offset, 24, 16, ent->viewheight-8+2);
+	VectorSet(offset, 24, 8, ent->viewheight-8+2);
+	VectorAdd (offset, g_offset, offset);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	if (ent->client->silencer_shots)
+		volume = 0.2;
+
+	gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
+	CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect);
+
+/*
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_BLASTER);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+*/
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+
+void CTFWeapon_Grapple_Fire (edict_t *ent)
+{
+	int		damage;
+
+	damage = 10;
+	CTFGrappleFire (ent, vec3_origin, damage, 0);
+	ent->client->ps.gunframe++;
+}
+
+void CTFWeapon_Grapple (edict_t *ent)
+{
+	static int	pause_frames[]	= {10, 18, 27, 0};
+	static int	fire_frames[]	= {6, 0};
+	int prevstate;
+
+	// if the the attack button is still down, stay in the firing frame
+	if ((ent->client->buttons & BUTTON_ATTACK) && 
+		ent->client->weaponstate == WEAPON_FIRING &&
+		ent->client->ctf_grapple)
+		ent->client->ps.gunframe = 9;
+
+	if (!(ent->client->buttons & BUTTON_ATTACK) && 
+		ent->client->ctf_grapple) {
+		CTFResetGrapple(ent->client->ctf_grapple);
+		if (ent->client->weaponstate == WEAPON_FIRING)
+			ent->client->weaponstate = WEAPON_READY;
+	}
+
+
+	if (ent->client->newweapon && 
+		ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY &&
+		ent->client->weaponstate == WEAPON_FIRING) {
+		// he wants to change weapons while grappled
+		ent->client->weaponstate = WEAPON_DROPPING;
+		ent->client->ps.gunframe = 32;
+	}
+
+	prevstate = ent->client->weaponstate;
+	Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, 
+		CTFWeapon_Grapple_Fire);
+
+	// if we just switched back to grapple, immediately go to fire frame
+	if (prevstate == WEAPON_ACTIVATING &&
+		ent->client->weaponstate == WEAPON_READY &&
+		ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
+		if (!(ent->client->buttons & BUTTON_ATTACK))
+			ent->client->ps.gunframe = 9;
+		else
+			ent->client->ps.gunframe = 5;
+		ent->client->weaponstate = WEAPON_FIRING;
+	}
+}
+
+void CTFTeam_f (edict_t *ent)
+{
+	char *t, *s;
+	int desired_team;
+
+	t = gi.args();
+	if (!*t) {
+	if (!ent->bot_info) 
+		gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n",
+			CTFTeamName(ent->client->resp.ctf_team));
+		return;
+	}
+	if (cistrcmp(t, "red") == 0)
+		desired_team = CTF_TEAM1;
+	else if (cistrcmp(t, "blue") == 0)
+		desired_team = CTF_TEAM2;
+	else {
+		if (!ent->bot_info) 
+          gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
+		return;
+	}
+
+	if (ent->client->resp.ctf_team == desired_team) {
+	if (!ent->bot_info) 
+		gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n",
+			CTFTeamName(ent->client->resp.ctf_team));
+		return;
+	}
+
+////
+	ent->svflags = 0;
+	ent->flags &= ~FL_GODMODE;
+	ent->client->resp.ctf_team = desired_team;
+	ent->client->resp.ctf_state = CTF_STATE_START;
+	s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
+	CTFAssignSkin(ent, s);
+
+	if (ent->solid == SOLID_NOT) { // spectator
+		PutClientInServer (ent);
+		// add a teleportation effect
+		ent->s.event = EV_PLAYER_TELEPORT;
+		// hold in place briefly
+		ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		ent->client->ps.pmove.pm_time = 14;
+		gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
+			ent->client->pers.netname, CTFTeamName(desired_team));
+		return;
+	}
+
+	ent->health = 0;
+	player_die (ent, ent, ent, 100000, vec3_origin);
+	// don't even bother waiting for death frames
+	ent->deadflag = DEAD_DEAD;
+	respawn (ent);
+
+	ent->client->resp.score = 0;
+
+	gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n",
+		ent->client->pers.netname, CTFTeamName(desired_team));
+}
+
+/*
+==================
+CTFScoreboardMessage
+==================
+*/
+void CTFScoreboardMessage (edict_t *, edict_t *)
+{
+	char	entry[1024];
+	char	string[1400];
+	int		len;
+	int		i, j, k, n;
+	int		sorted[2][MAX_CLIENTS];
+	int		sortedscores[2][MAX_CLIENTS];
+	int		score, total[2], totalscore[2];
+	int		last[2];
+	gclient_t	*cl;
+	edict_t		*cl_ent;
+	int team;
+	int maxsize = 1000;
+
+	// sort the clients by team and score
+	total[0] = total[1] = 0;
+	last[0] = last[1] = 0;
+	totalscore[0] = totalscore[1] = 0;
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		cl_ent = g_edicts + 1 + i;
+		if (!cl_ent->inuse)
+			continue;
+		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
+			team = 0;
+		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
+			team = 1;
+		else
+			continue; // unknown team?
+
+		score = game.clients[i].resp.score;
+		for (j=0 ; j<total[team] ; j++)
+		{
+			if (score > sortedscores[team][j])
+				break;
+		}
+		for (k=total[team] ; k>j ; k--)
+		{
+			sorted[team][k] = sorted[team][k-1];
+			sortedscores[team][k] = sortedscores[team][k-1];
+		}
+		sorted[team][j] = i;
+		sortedscores[team][j] = score;
+		totalscore[team] += score;
+		total[team]++;
+	}
+
+	// print level name and exit rules
+	// add the clients in sorted order
+	*string = 0;
+
+	// team one
+	sprintf(string, "if 24 xv 8 yv 8 pic 24 endif "
+		"xv 40 yv 28 string \"%4d/%-3d\" "
+		"xv 98 yv 12 num 2 18 "
+		"if 25 xv 168 yv 8 pic 25 endif "
+		"xv 200 yv 28 string \"%4d/%-3d\" "
+		"xv 256 yv 12 num 2 20 ",
+		totalscore[0], total[0],
+		totalscore[1], total[1]);
+	len = strlen(string);
+
+	for (i=0 ; i<16 ; i++)
+	{
+		if (i >= total[0] && i >= total[1])
+			break; // we're done
+
+/* ifndef NEW_SCORE
+		// set up y
+		sprintf(entry, "yv %d ", 42 + i * 8);
+		if (maxsize - len > strlen(entry)) {
+			strcat(string, entry);
+			len = strlen(string);
+		}
+*/
+		*entry = 0;
+
+		// left side
+		if (i < total[0]) {
+			cl = &game.clients[sorted[0][i]];
+			cl_ent = g_edicts + 1 + sorted[0][i];
+
+/* ifndef NEW_SCORE
+			sprintf(entry+strlen(entry),
+			"xv 0 %s \"%3d %3d %-12.12s\" ",
+			(cl_ent == ent) ? "string2" : "string",
+			cl->resp.score, 
+			(cl->ping > 999) ? 999 : cl->ping, 
+			cl->pers.netname);
+
+			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
+				strcat(entry, "xv 56 picn sbfctf2 ");
+*/
+			sprintf(entry+strlen(entry),
+				"ctf 0 %d %d %d %d ",
+				42 + i * 8,
+				sorted[0][i],
+				cl->resp.score,
+				cl->ping > 999 ? 999 : cl->ping);
+
+			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
+				sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ",
+					42 + i * 8);
+
+			if (maxsize - len > strlen(entry)) {
+				strcat(string, entry);
+				len = strlen(string);
+				last[0] = i;
+			}
+		}
+
+		// right side
+		if (i < total[1]) {
+			cl = &game.clients[sorted[1][i]];
+			cl_ent = g_edicts + 1 + sorted[1][i];
+
+/* ifndef NEW_SCORE
+			sprintf(entry+strlen(entry),
+			"xv 160 %s \"%3d %3d %-12.12s\" ",
+			(cl_ent == ent) ? "string2" : "string",
+			cl->resp.score, 
+			(cl->ping > 999) ? 999 : cl->ping, 
+			cl->pers.netname);
+
+			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
+				strcat(entry, "xv 216 picn sbfctf1 ");
+
+*/
+			sprintf(entry+strlen(entry),
+				"ctf 160 %d %d %d %d ",
+				42 + i * 8,
+				sorted[1][i],
+				cl->resp.score,
+				cl->ping > 999 ? 999 : cl->ping);
+
+			if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
+				sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ",
+					42 + i * 8);
+			if (maxsize - len > strlen(entry)) {
+				strcat(string, entry);
+				len = strlen(string);
+				last[1] = i;
+			}
+		}
+	}
+
+	// put in spectators if we have enough room
+	if (last[0] > last[1])
+		j = last[0];
+	else
+		j = last[1];
+	j = (j + 2) * 8 + 42;
+
+	k = n = 0;
+	if (maxsize - len > 50) {
+		for (i = 0; i < maxclients->value; i++) {
+			cl_ent = g_edicts + 1 + i;
+			cl = &game.clients[i];
+			if (!cl_ent->inuse ||
+				cl_ent->solid != SOLID_NOT ||
+				cl_ent->client->resp.ctf_team != CTF_NOTEAM)
+				continue;
+
+			if (!k) {
+				k = 1;
+				sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j);
+				strcat(string, entry);
+				len = strlen(string);
+				j += 8;
+			}
+
+			sprintf(entry+strlen(entry),
+				"ctf %d %d %d %d %d ",
+				(n & 1) ? 160 : 0, // x
+				j, // y
+				i, // playernum
+				cl->resp.score,
+				cl->ping > 999 ? 999 : cl->ping);
+			if (maxsize - len > strlen(entry)) {
+				strcat(string, entry);
+				len = strlen(string);
+			}
+			
+			if (n & 1)
+				j += 8;
+			n++;
+		}
+	}
+
+	if (total[0] - last[0] > 1) // couldn't fit everyone
+		sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ",
+			42 + (last[0]+1)*8, total[0] - last[0] - 1);
+	if (total[1] - last[1] > 1) // couldn't fit everyone
+		sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ",
+			42 + (last[1]+1)*8, total[1] - last[1] - 1);
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+}
+
+/*------------------------------------------------------------------------*/
+/* TECH																	  */
+/*------------------------------------------------------------------------*/
+
+void CTFHasTech(edict_t *who)
+{
+	if (level.time - who->client->ctf_lasttechmsg > 2) {
+		if (!who->bot_info) gi.centerprintf(who, "You already have a TECH powerup.");
+		who->client->ctf_lasttechmsg = level.time;
+	}
+}
+
+gitem_t *CTFWhat_Tech(edict_t *ent)
+{
+	gitem_t *tech;
+	int i;
+
+	i = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+			return tech;
+		}
+		i++;
+	}
+	return NULL;
+}
+
+qboolean CTFPickup_Tech (edict_t *ent, edict_t *other)
+{
+	gitem_t *tech;
+	int i;
+
+	i = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			other->client->pers.inventory[ITEM_INDEX(tech)]) {
+			CTFHasTech(other);
+			return false; // has this one
+		}
+		i++;
+	}
+	
+	// client only gets one tech
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+	other->client->ctf_regentime = level.time;
+	return true;
+}
+
+static void SpawnTech(gitem_t *item, edict_t *spot);
+
+static edict_t *FindTechSpawn(void)
+{
+	edict_t *spot = NULL;
+	int i = rand() % 16;
+
+	while (i--)
+		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+	if (!spot)
+		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+	return spot;
+}
+
+static void TechThink(edict_t *tech)
+{
+	edict_t *spot;
+
+	if ((spot = FindTechSpawn()) != NULL) {
+		SpawnTech(tech->item, spot);
+		G_FreeEdict(tech);
+	} else {
+		tech->nextthink = level.time + CTF_TECH_TIMEOUT;
+		tech->think = TechThink;
+	}
+}
+
+void CTFDrop_Tech(edict_t *ent, gitem_t *item)
+{
+	edict_t *tech;
+
+	tech = Drop_Item(ent, item);
+	tech->nextthink = level.time + CTF_TECH_TIMEOUT;
+	tech->think = TechThink;
+	ent->client->pers.inventory[ITEM_INDEX(item)] = 0;
+}
+
+void CTFDeadDropTech(edict_t *ent)
+{
+	gitem_t *tech;
+	edict_t *dropped;
+	int i;
+
+	i = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+			dropped = Drop_Item(ent, tech);
+			// hack the velocity to make it bounce random
+			dropped->velocity[0] = (rand() % 600) - 300;
+			dropped->velocity[1] = (rand() % 600) - 300;
+			dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
+			dropped->think = TechThink;
+			dropped->owner = NULL;
+			ent->client->pers.inventory[ITEM_INDEX(tech)] = 0;
+		}
+		i++;
+	}
+}
+
+static void SpawnTech(gitem_t *item, edict_t *spot)
+{
+	edict_t	*ent;
+	vec3_t	forward, right;
+	vec3_t  angles;
+
+	ent = G_Spawn();
+
+	ent->classname = item->classname;
+	ent->item = item;
+	ent->spawnflags = DROPPED_ITEM;
+	ent->s.effects = item->world_model_flags;
+	ent->s.renderfx = RF_GLOW;
+	VectorSet (ent->mins, -15, -15, -15);
+	VectorSet (ent->maxs, 15, 15, 15);
+	gi.setmodel (ent, ent->item->world_model);
+	ent->solid = SOLID_TRIGGER;
+	ent->movetype = MOVETYPE_TOSS;  
+	ent->touch = Touch_Item;
+	ent->owner = ent;
+
+	angles[0] = 0;
+	angles[1] = rand() % 360;
+	angles[2] = 0;
+
+	AngleVectors (angles, forward, right, NULL);
+	VectorCopy (spot->s.origin, ent->s.origin);
+	ent->s.origin[2] += 16;
+	VectorScale (forward, 100, ent->velocity);
+	ent->velocity[2] = 300;
+
+	ent->nextthink = level.time + CTF_TECH_TIMEOUT;
+	ent->think = TechThink;
+
+	gi.linkentity (ent);
+}
+
+static void SpawnTechs(edict_t *)
+{
+	gitem_t *tech;
+	edict_t *spot;
+	int i;
+
+	i = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			(spot = FindTechSpawn()) != NULL)
+			SpawnTech(tech, spot);
+		i++;
+	}
+}
+
+// frees the passed edict!
+void CTFRespawnTech(edict_t *ent)
+{
+	edict_t *spot;
+
+	if ((spot = FindTechSpawn()) != NULL)
+		SpawnTech(ent->item, spot);
+	G_FreeEdict(ent);
+}
+
+void CTFSetupTechSpawn(void)
+{
+	edict_t *ent;
+
+    if (!((int)ctf->value) && (int)(no_tech->value)) return;
+	if (techspawn || ((int)dmflags->value & DF_CTF_NO_TECH))
+		return;
+
+	ent = G_Spawn();
+	ent->nextthink = level.time + 2;
+	ent->think = SpawnTechs;
+	techspawn = true;
+}
+
+int CTFApplyResistance(edict_t *ent, int dmg)
+{
+	static gitem_t *tech = NULL;
+	float volume = 1.0;
+
+	if (ent->client && ent->client->silencer_shots)
+		volume = 0.2;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech1");
+	if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+		// make noise
+	   	gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
+		return dmg / 2;
+	}
+	return dmg;
+}
+
+int CTFApplyStrength(edict_t *ent, int dmg)
+{
+	static gitem_t *tech = NULL;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech2");
+	if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+		return dmg * 2;
+	}
+	return dmg;
+}
+
+qboolean CTFApplyStrengthSound(edict_t *ent)
+{
+	static gitem_t *tech = NULL;
+	float volume = 1.0;
+
+	if (ent->client && ent->client->silencer_shots)
+		volume = 0.2;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech2");
+	if (tech && ent->client &&
+		ent->client->pers.inventory[ITEM_INDEX(tech)]) {
+		if (ent->client->ctf_techsndtime < level.time) {
+			ent->client->ctf_techsndtime = level.time + 1;
+			if (ent->client->quad_framenum > level.framenum)
+				gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
+			else
+				gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
+		}
+		return true;
+	}
+	return false;
+}
+
+
+qboolean CTFApplyHaste(edict_t *ent)
+{
+	static gitem_t *tech = NULL;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech3");
+	if (tech && ent->client &&
+		ent->client->pers.inventory[ITEM_INDEX(tech)])
+		return true;
+	return false;
+}
+
+void CTFApplyHasteSound(edict_t *ent)
+{
+	static gitem_t *tech = NULL;
+	float volume = 1.0;
+
+	if (ent->client && ent->client->silencer_shots)
+		volume = 0.2;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech3");
+	if (tech && ent->client &&
+		ent->client->pers.inventory[ITEM_INDEX(tech)] &&
+		ent->client->ctf_techsndtime < level.time) {
+		ent->client->ctf_techsndtime = level.time + 1;
+		gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
+	}
+}
+
+void CTFApplyRegeneration(edict_t *ent)
+{
+	static gitem_t *tech = NULL;
+	qboolean noise = false;
+	gclient_t *client;
+	int index;
+	float volume = 1.0;
+
+	client = ent->client;
+	if (!client)
+		return;
+
+	if (ent->client->silencer_shots)
+		volume = 0.2;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech4");
+	if (tech && client->pers.inventory[ITEM_INDEX(tech)]) {
+		if (client->ctf_regentime < level.time) {
+			client->ctf_regentime = level.time;
+			if (ent->health < 150) {
+				ent->health += 5;
+				if (ent->health > 150)
+					ent->health = 150;
+				client->ctf_regentime += 0.5;
+				noise = true;
+			}
+			index = ArmorIndex (ent);
+			if (index && client->pers.inventory[index] < 150) {
+				client->pers.inventory[index] += 5;
+				if (client->pers.inventory[index] > 150)
+					client->pers.inventory[index] = 150;
+				client->ctf_regentime += 0.5;
+				noise = true;
+			}
+		}
+		if (noise && ent->client->ctf_techsndtime < level.time) {
+			ent->client->ctf_techsndtime = level.time + 1;
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
+		}
+	}
+}
+
+qboolean CTFHasRegeneration(edict_t *ent)
+{
+	static gitem_t *tech = NULL;
+
+	if (!tech)
+		tech = FindItemByClassname("item_tech4");
+	if (tech && ent->client &&
+		ent->client->pers.inventory[ITEM_INDEX(tech)])
+		return true;
+	return false;
+}
+
+/*
+======================================================================
+
+SAY_TEAM
+
+======================================================================
+*/
+
+// This array is in 'importance order', it indicates what items are
+// more important when reporting their names.
+struct {
+	char *classname;
+	int priority;
+} loc_names[] = 
+{
+	{	"item_flag_team1",			1 },
+	{	"item_flag_team2",			1 },
+	{	"item_quad",				2 }, 
+	{	"item_invulnerability",		2 },
+	{	"weapon_bfg",				3 },
+	{	"weapon_railgun",			4 },
+	{	"weapon_rocketlauncher",	4 },
+	{	"weapon_hyperblaster",		4 },
+	{	"weapon_chaingun",			4 },
+	{	"weapon_grenadelauncher",	4 },
+	{	"weapon_machinegun",		4 },
+	{	"weapon_supershotgun",		4 },
+	{	"weapon_shotgun",			4 },
+	{	"item_power_screen",		5 },
+	{	"item_power_shield",		5 },
+	{	"item_armor_body",			6 },
+	{	"item_armor_combat",		6 },
+	{	"item_armor_jacket",		6 },
+	{	"item_silencer",			7 },
+	{	"item_breather",			7 },
+	{	"item_enviro",				7 },
+	{	"item_adrenaline",			7 },
+	{	"item_bandolier",			8 },
+	{	"item_pack",				8 },
+	{ NULL, 0 }
+};
+
+
+static void CTFSay_Team_Location(edict_t *who, char *buf)
+{
+	edict_t *what = NULL;
+	edict_t *hot = NULL;
+	float hotdist = 999999, newdist;
+	vec3_t v;
+	int hotindex = 999;
+	int i;
+	gitem_t *item;
+	int nearteam = -1;
+	edict_t *flag1, *flag2;
+	qboolean hotsee = false;
+	qboolean cansee;
+
+	while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) {
+		// find what in loc_classnames
+		for (i = 0; loc_names[i].classname; i++)
+			if (strcmp(what->classname, loc_names[i].classname) == 0)
+				break;
+		if (!loc_names[i].classname)
+			continue;
+		// something we can see get priority over something we can't
+		cansee = loc_CanSee(what, who);
+		if (cansee && !hotsee) {
+			hotsee = true;
+			hotindex = loc_names[i].priority;
+			hot = what;
+			VectorSubtract(what->s.origin, who->s.origin, v);
+			hotdist = VectorLength(v);
+			continue;
+		}
+		// if we can't see this, but we have something we can see, skip it
+		if (hotsee && !cansee)
+			continue;
+		if (hotsee && hotindex < loc_names[i].priority)
+			continue;
+		VectorSubtract(what->s.origin, who->s.origin, v);
+		newdist = VectorLength(v);
+		if (newdist < hotdist || 
+			(cansee && loc_names[i].priority < hotindex)) {
+			hot = what;
+			hotdist = newdist;
+			hotindex = i;
+			hotsee = loc_CanSee(hot, who);
+		}
+	}
+
+	if (!hot) {
+		strcpy(buf, "nowhere");
+		return;
+	}
+
+	// we now have the closest item
+	// see if there's more than one in the map, if so
+	// we need to determine what team is closest
+	what = NULL;
+	while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) {
+		if (what == hot)
+			continue;
+		// if we are here, there is more than one, find out if hot
+		// is closer to red flag or blue flag
+		if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL &&
+			(flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) {
+			VectorSubtract(hot->s.origin, flag1->s.origin, v);
+			hotdist = VectorLength(v);
+			VectorSubtract(hot->s.origin, flag2->s.origin, v);
+			newdist = VectorLength(v);
+			if (hotdist < newdist)
+				nearteam = CTF_TEAM1;
+			else if (hotdist > newdist)
+				nearteam = CTF_TEAM2;
+		}
+		break;
+	}
+
+	if ((item = FindItemByClassname(hot->classname)) == NULL) {
+		strcpy(buf, "nowhere");
+		return;
+	}
+
+	// in water?
+	if (who->waterlevel)
+		strcpy(buf, "in the water ");
+	else
+		*buf = 0;
+
+	// near or above
+	VectorSubtract(who->s.origin, hot->s.origin, v);
+	if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1]))
+		if (v[2] > 0)
+			strcat(buf, "above ");
+		else
+			strcat(buf, "below ");
+	else
+		strcat(buf, "near ");
+
+	if (nearteam == CTF_TEAM1)
+		strcat(buf, "the red ");
+	else if (nearteam == CTF_TEAM2)
+		strcat(buf, "the blue ");
+	else
+		strcat(buf, "the ");
+
+	strcat(buf, item->pickup_name);
+}
+
+static void CTFSay_Team_Armor(edict_t *who, char *buf)
+{
+	gitem_t		*item;
+	int			index, cells;
+	int			power_armor_type;
+
+	*buf = 0;
+
+	power_armor_type = PowerArmorType (who);
+	if (power_armor_type)
+	{
+		cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
+		if (cells)
+			sprintf(buf+strlen(buf), "%s with %i cells ",
+				(power_armor_type == POWER_ARMOR_SCREEN) ?
+				"Power Screen" : "Power Shield", cells);
+	}
+
+	index = ArmorIndex (who);
+	if (index)
+	{
+		item = GetItemByIndex (index);
+		if (item) {
+			if (*buf)
+				strcat(buf, "and ");
+			sprintf(buf+strlen(buf), "%i units of %s",
+				who->client->pers.inventory[index], item->pickup_name);
+		}
+	}
+
+	if (!*buf)
+		strcpy(buf, "no armor");
+}
+
+static void CTFSay_Team_Health(edict_t *who, char *buf)
+{
+	if (who->health <= 0)
+		strcpy(buf, "dead");
+	else
+		sprintf(buf, "%i health", who->health);
+}
+
+static void CTFSay_Team_Tech(edict_t *who, char *buf)
+{
+	gitem_t *tech;
+	int i;
+
+	// see if the player has a tech powerup
+	i = 0;
+	while (tnames[i]) {
+		if ((tech = FindItemByClassname(tnames[i])) != NULL &&
+			who->client->pers.inventory[ITEM_INDEX(tech)]) {
+			sprintf(buf, "the %s", tech->pickup_name);
+			return;
+		}
+		i++;
+	}
+	strcpy(buf, "no powerup");
+}
+
+static void CTFSay_Team_Weapon(edict_t *who, char *buf)
+{
+	if (who->client->pers.weapon)
+		strcpy(buf, who->client->pers.weapon->pickup_name);
+	else
+		strcpy(buf, "none");
+}
+
+static void CTFSay_Team_Sight(edict_t *who, char *buf)
+{
+	int i;
+	edict_t *targ;
+	int n = 0;
+	char s[1024];
+	char s2[1024];
+
+	*s = *s2 = 0;
+	for (i = 1; i <= maxclients->value; i++) {
+		targ = g_edicts + i;
+		if (!targ->inuse || 
+			targ == who ||
+			!loc_CanSee(targ, who))
+			continue;
+		if (*s2) {
+			if (strlen(s) + strlen(s2) + 3 < sizeof(s)) {
+				if (n)
+					strcat(s, ", ");
+				strcat(s, s2);
+				*s2 = 0;
+			}
+			n++;
+		}
+		strcpy(s2, targ->client->pers.netname);
+	}
+	if (*s2) {
+		if (strlen(s) + strlen(s2) + 6 < sizeof(s)) {
+			if (n)
+				strcat(s, " and ");
+			strcat(s, s2);
+		}
+		strcpy(buf, s);
+	} else
+		strcpy(buf, "no one");
+}
+
+void CTFSay_Team(edict_t *who, char *msg)
+{
+	char outmsg[1024];
+	char buf[1024];
+	int i;
+	char *p;
+	edict_t *cl_ent;
+
+	outmsg[0] = 0;
+
+	if (*msg == '\"') {
+		msg[strlen(msg) - 1] = 0;
+		msg++;
+	}
+
+	for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) {
+		if (*msg == '%') {
+			switch (*++msg) {
+				case 'l' :
+				case 'L' :
+					CTFSay_Team_Location(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+				case 'a' :
+				case 'A' :
+					CTFSay_Team_Armor(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+				case 'h' :
+				case 'H' :
+					CTFSay_Team_Health(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+				case 't' :
+				case 'T' :
+					CTFSay_Team_Tech(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+				case 'w' :
+				case 'W' :
+					CTFSay_Team_Weapon(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+
+				case 'n' :
+				case 'N' :
+					CTFSay_Team_Sight(who, buf);
+					strcpy(p, buf);
+					p += strlen(buf);
+					break;
+
+				default :
+					*p++ = *msg;
+			}
+		} else
+			*p++ = *msg;
+	}
+	*p = 0;
+
+	for (i = 0; i < maxclients->value; i++) {
+		cl_ent = g_edicts + 1 + i;
+		if (!cl_ent->inuse)
+			continue;
+		if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) {
+          if (!cl_ent->bot_info) 
+			gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", 
+				who->client->pers.netname, outmsg);
+          }
+	}
+}
+
+/*-----------------------------------------------------------------------*/
+/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
+The origin is the bottom of the banner.
+The banner is 248 tall.
+*/
+static void misc_ctf_banner_think (edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 16;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_ctf_banner (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2");
+	if (ent->spawnflags & 1) // team2
+		ent->s.skinnum = 1;
+
+	ent->s.frame = rand() % 16;
+	gi.linkentity (ent);
+
+	ent->think = misc_ctf_banner_think;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
+The origin is the bottom of the banner.
+The banner is 124 tall.
+*/
+void SP_misc_ctf_small_banner (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2");
+	if (ent->spawnflags & 1) // team2
+		ent->s.skinnum = 1;
+
+	ent->s.frame = rand() % 16;
+	gi.linkentity (ent);
+
+	ent->think = misc_ctf_banner_think;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+
+/*-----------------------------------------------------------------------*/
+
+void CTFJoinTeam(edict_t *ent, int desired_team)
+{
+	char *s;
+
+	PMenu_Close(ent);
+
+    ent->client->pers.team_no = (desired_team==CTF_TEAM1) ? 1 : 2;
+    if (ent->bot_pers) ent->bot_pers->team_no = ent->client->pers.team_no;
+
+	ent->svflags &= ~SVF_NOCLIENT;
+	ent->client->resp.ctf_team = desired_team;
+	ent->client->resp.ctf_state = CTF_STATE_START;
+	s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
+	CTFAssignSkin(ent, s);
+
+	PutClientInServer (ent);
+	// add a teleportation effect
+	ent->s.event = EV_PLAYER_TELEPORT;
+	// hold in place briefly
+	ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+	ent->client->ps.pmove.pm_time = 14;
+	gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n",
+		ent->client->pers.netname, CTFTeamName(desired_team));
+
+    if (!ent->bot_info) gi.centerprintf( ent, CRBOT_INFO );
+}
+
+void CTFJoinTeam1(edict_t *ent, pmenu_t *)
+{
+	CTFJoinTeam(ent, CTF_TEAM1);
+}
+
+void CTFJoinTeam2(edict_t *ent, pmenu_t *)
+{
+	CTFJoinTeam(ent, CTF_TEAM2);
+}
+
+void CTFChaseCam(edict_t *ent, pmenu_t *)
+{
+	int i;
+	edict_t *e;
+
+	if (ent->client->chase_target) {
+		ent->client->chase_target = NULL;
+		PMenu_Close(ent);
+		return;
+	}
+
+	for (i = 1; i <= maxclients->value; i++) {
+		e = g_edicts + i;
+		if (e->inuse && e->solid != SOLID_NOT) {
+			ent->client->chase_target = e;
+			PMenu_Close(ent);
+			ent->client->update_chase = true;
+			break;
+		}
+	}
+}
+
+void CTFReturnToMain(edict_t *ent, pmenu_t *)
+{
+	PMenu_Close(ent);
+	CTFOpenJoinMenu(ent);
+}
+
+void CTFCredits(edict_t *ent, pmenu_t *);
+
+void DeathmatchScoreboard (edict_t *ent);
+
+void CTFShowScores(edict_t *ent, pmenu_t *)
+{
+	PMenu_Close(ent);
+
+	ent->client->showscores = true;
+	ent->client->showinventory = false;
+	DeathmatchScoreboard (ent);
+}
+
+pmenu_t creditsmenu[] = {
+	{ "*Quake II",						PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*ThreeWave Capture the Flag",	PMENU_ALIGN_CENTER, NULL, NULL },
+	{ NULL,								PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*Programming",					PMENU_ALIGN_CENTER, NULL, NULL }, 
+	{ "Dave 'Zoid' Kirsch",				PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*Level Design", 					PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Christian Antkow",				PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Tim Willits",					PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Dave 'Zoid' Kirsch",				PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*Art",							PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Adrian Carmack Paul Steed",		PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Kevin Cloud",					PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*Sound",							PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Tom 'Bjorn' Klok",				PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*Original CTF Art Design",		PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Brian 'Whaleboy' Cozzens",		PMENU_ALIGN_CENTER, NULL, NULL },
+	{ NULL,								PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Return to Main Menu",			PMENU_ALIGN_LEFT, NULL, CTFReturnToMain }
+};
+
+
+pmenu_t joinmenu[] = {
+	{ "*CRbot " CRBOT_VER " for Quake II",			PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "*ThreeWave Capture the Flag",	PMENU_ALIGN_CENTER, NULL, NULL },
+	{ NULL,					PMENU_ALIGN_CENTER, NULL, NULL },
+	{ NULL,					PMENU_ALIGN_CENTER, NULL, NULL },
+	{ "Join Red Team",		PMENU_ALIGN_LEFT, NULL, CTFJoinTeam1 },
+	{ NULL,					PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "Join Blue Team",		PMENU_ALIGN_LEFT, NULL, CTFJoinTeam2 },
+	{ NULL,					PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "Chase Camera",		PMENU_ALIGN_LEFT, NULL, CTFChaseCam },
+	{ "Credits",			PMENU_ALIGN_LEFT, NULL, CTFCredits },
+	{ NULL,					PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "Use [ and ] to move cursor",	PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "ENTER to select",	PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "ESC to Exit Menu",	PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "(TAB to Return)",	PMENU_ALIGN_LEFT, NULL, NULL },
+	{ NULL,					PMENU_ALIGN_LEFT, NULL, NULL },
+	{ "v" CTF_STRING_VERSION,	PMENU_ALIGN_RIGHT, NULL, NULL },
+};
+
+int CTFUpdateJoinMenu(edict_t *ent)
+{
+	static char levelname[32];
+	static char team1players[32];
+	static char team2players[32];
+	int num1, num2, i;
+
+	joinmenu[4].text = "Join Red Team";
+	joinmenu[4].SelectFunc = CTFJoinTeam1;
+	joinmenu[6].text = "Join Blue Team";
+	joinmenu[6].SelectFunc = CTFJoinTeam2;
+
+	if (ctf_forcejoin->string && *ctf_forcejoin->string) {
+		if (cistrcmp(ctf_forcejoin->string, "red") == 0) {
+			joinmenu[6].text = NULL;
+			joinmenu[6].SelectFunc = NULL;
+		} else if (cistrcmp(ctf_forcejoin->string, "blue") == 0) {
+			joinmenu[4].text = NULL;
+			joinmenu[4].SelectFunc = NULL;
+		}
+	}
+
+	if (ent->client->chase_target)
+		joinmenu[8].text = "Leave Chase Camera";
+	else
+		joinmenu[8].text = "Chase Camera";
+
+	levelname[0] = '*';
+	if (g_edicts[0].message)
+		strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
+	else
+		strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
+	levelname[sizeof(levelname) - 1] = 0;
+
+	num1 = num2 = 0;
+	for (i = 0; i < maxclients->value; i++) {
+		if (!g_edicts[i+1].inuse)
+			continue;
+		if (game.clients[i].resp.ctf_team == CTF_TEAM1)
+			num1++;
+		else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
+			num2++;
+	}
+
+	sprintf(team1players, "  (%d players)", num1);
+	sprintf(team2players, "  (%d players)", num2);
+
+	joinmenu[2].text = levelname;
+	if (joinmenu[4].text)
+		joinmenu[5].text = team1players;
+	else
+		joinmenu[5].text = NULL;
+	if (joinmenu[6].text)
+		joinmenu[7].text = team2players;
+	else
+		joinmenu[7].text = NULL;
+	
+	if (num1 > num2)
+		return CTF_TEAM1;
+	else if (num2 > num1)
+		return CTF_TEAM1;
+	return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2;
+}
+
+void CTFOpenJoinMenu(edict_t *ent)
+{
+	int team;
+
+	team = CTFUpdateJoinMenu(ent);
+	if (ent->client->chase_target)
+		team = 8;
+	else if (team == CTF_TEAM1)
+		team = 4;
+	else
+		team = 6;
+	PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t));
+}
+
+void CTFCredits(edict_t *ent, pmenu_t *)
+{
+	PMenu_Close(ent);
+	PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t));
+}
+
+qboolean CTFStartClient(edict_t *ent)
+{
+	if (ent->client->resp.ctf_team != CTF_NOTEAM)
+		return false;
+
+	if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) {
+		// start as 'observer'
+		ent->movetype = MOVETYPE_NOCLIP;
+		ent->solid = SOLID_NOT;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->client->resp.ctf_team = CTF_NOTEAM;
+		ent->client->ps.gunindex = 0;
+		gi.linkentity (ent);
+
+		CTFOpenJoinMenu(ent);
+		return true;
+	}
+	return false;
+}
+
+qboolean CTFCheckRules(void)
+{
+	if (capturelimit->value && 
+		(ctfgame.team1 >= capturelimit->value ||
+		ctfgame.team2 >= capturelimit->value)) {
+		gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n");
+		return true;
+	}
+	return false;
+}
+
+/*--------------------------------------------------------------------------
+ * just here to help old map conversions
+ *--------------------------------------------------------------------------*/
+
+static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t		*dest;
+	int			i;
+	vec3_t		forward;
+
+	if (!other->client)
+		return;
+	dest = G_Find (NULL, FOFS(targetname), self->target);
+	if (!dest)
+	{
+		gi.dprintf ("Couldn't find destination\n");
+		return;
+	}
+
+//ZOID
+	CTFPlayerResetGrapple(other);
+//ZOID
+
+	// unlink to make sure it can't possibly interfere with KillBox
+	gi.unlinkentity (other);
+
+	VectorCopy (dest->s.origin, other->s.origin);
+	VectorCopy (dest->s.origin, other->s.old_origin);
+	other->s.origin[2] += 15;
+
+	// clear the velocity and hold them in place briefly
+	VectorClear (other->velocity);
+	other->client->ps.pmove.pm_time = 160>>3;		// hold time
+	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+	// draw the teleport splash at source and on the player
+	self->enemy->s.event = EV_PLAYER_TELEPORT;
+	other->s.event = EV_PLAYER_TELEPORT;
+
+	// set angles
+	for (i=0 ; i<3 ; i++)
+		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+
+	other->s.angles[PITCH] = 0;
+	other->s.angles[YAW] = dest->s.angles[YAW];
+	other->s.angles[ROLL] = 0;
+	VectorCopy (dest->s.angles, other->client->ps.viewangles);
+	VectorCopy (dest->s.angles, other->client->v_angle);
+
+	// give a little forward velocity
+	AngleVectors (other->client->v_angle, forward, NULL, NULL);
+	VectorScale(forward, 200, other->velocity);
+
+	// kill anything at the destination
+	if (!KillBox (other))
+	{
+	}
+
+	gi.linkentity (other);
+}
+
+/*QUAKED trigger_teleport (0.5 0.5 0.5) ?
+Players touching this will be teleported
+*/
+void SP_trigger_teleport (edict_t *ent)
+{
+	edict_t *s;
+	int i;
+
+	if (!ent->target)
+	{
+		gi.dprintf ("teleporter without a target.\n");
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->svflags |= SVF_NOCLIENT;
+	ent->solid = SOLID_TRIGGER;
+	ent->touch = old_teleporter_touch;
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+
+	// noise maker and splash effect dude
+	s = G_Spawn();
+	ent->enemy = s;
+	for (i = 0; i < 3; i++)
+		s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2;
+	s->s.sound = gi.soundindex ("world/hum1.wav");
+	gi.linkentity(s);
+	
+}
+
+/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
+Point trigger_teleports at these.
+*/
+void SP_info_teleport_destination (edict_t *ent)
+{
+	ent->s.origin[2] += 16;
+}
+
--- /dev/null
+++ b/crbot/g_func.c
@@ -1,0 +1,2037 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+=========================================================
+
+  PLATS
+
+  movement options:
+
+  linear
+  smooth start, hard stop
+  smooth start, smooth stop
+
+  start
+  end
+  acceleration
+  speed
+  deceleration
+  begin sound
+  end sound
+  target fired when reaching end
+  wait at end
+
+  object characteristics that use move segments
+  ---------------------------------------------
+  movetype_push, or movetype_stop
+  action when touched
+  action when blocked
+  action when used
+	disabled?
+  auto trigger spawning
+
+
+=========================================================
+*/
+
+#define PLAT_LOW_TRIGGER	1
+
+#define	STATE_TOP			0
+#define	STATE_BOTTOM		1
+#define STATE_UP			2
+#define STATE_DOWN			3
+
+#define DOOR_START_OPEN		1
+#define DOOR_REVERSE		2
+#define DOOR_CRUSHER		4
+#define DOOR_NOMONSTER		8
+#define DOOR_TOGGLE			32
+#define DOOR_X_AXIS			64
+#define DOOR_Y_AXIS			128
+
+
+//
+// Support routines for movement (changes in origin using velocity)
+//
+
+void Move_Done (edict_t *ent)
+{
+	VectorClear (ent->velocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void Move_Final (edict_t *ent)
+{
+	if (ent->moveinfo.remaining_distance == 0)
+	{
+		Move_Done (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
+
+	ent->think = Move_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void Move_Begin (edict_t *ent)
+{
+	float	frames;
+
+	if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
+	{
+		Move_Final (ent);
+		return;
+	}
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
+	frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
+	ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
+	ent->nextthink = level.time + (frames * FRAMETIME);
+	ent->think = Move_Final;
+}
+
+void Think_AccelMove (edict_t *ent);
+
+void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
+{
+	VectorClear (ent->velocity);
+	VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
+	ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
+	ent->moveinfo.endfunc = func;
+
+	if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
+	{
+		if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+		{
+			Move_Begin (ent);
+		}
+		else
+		{
+			ent->nextthink = level.time + FRAMETIME;
+			ent->think = Move_Begin;
+		}
+	}
+	else
+	{
+		// accelerative
+		ent->moveinfo.current_speed = 0;
+		ent->think = Think_AccelMove;
+		ent->nextthink = level.time + FRAMETIME;
+	}
+}
+
+
+//
+// Support routines for angular movement (changes in angle using avelocity)
+//
+
+void AngleMove_Done (edict_t *ent)
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void AngleMove_Final (edict_t *ent)
+{
+	vec3_t	move;
+
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
+
+	if (VectorCompare (move, vec3_origin))
+	{
+		AngleMove_Done (ent);
+		return;
+	}
+
+	VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
+
+	ent->think = AngleMove_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void AngleMove_Begin (edict_t *ent)
+{
+	vec3_t	destdelta;
+	float	len;
+	float	traveltime;
+	float	frames;
+
+	// set destdelta to the vector needed to move
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
+	
+	// calculate length of vector
+	len = VectorLength (destdelta);
+	
+	// divide by speed to get time to reach dest
+	traveltime = len / ent->moveinfo.speed;
+
+	if (traveltime < FRAMETIME)
+	{
+		AngleMove_Final (ent);
+		return;
+	}
+
+	frames = floor(traveltime / FRAMETIME);
+
+	// scale the destdelta vector by the time spent traveling to get velocity
+	VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
+
+	// set nextthink to trigger a think when dest is reached
+	ent->nextthink = level.time + frames * FRAMETIME;
+	ent->think = AngleMove_Final;
+}
+
+void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc = func;
+	if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+	{
+		AngleMove_Begin (ent);
+	}
+	else
+	{
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = AngleMove_Begin;
+	}
+}
+
+
+/*
+==============
+Think_AccelMove
+
+The team has completed a frame of movement, so
+change the speed for the next frame
+==============
+*/
+#define AccelerationDistance(target, rate)	(target * ((target / rate) + 1) / 2)
+
+void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
+{
+	float	accel_dist;
+	float	decel_dist;
+
+	moveinfo->move_speed = moveinfo->speed;
+
+	if (moveinfo->remaining_distance < moveinfo->accel)
+	{
+		moveinfo->current_speed = moveinfo->remaining_distance;
+		return;
+	}
+
+	accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
+	decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
+
+	if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
+	{
+		float	f;
+
+		f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
+		moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
+		decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
+	}
+
+	moveinfo->decel_distance = decel_dist;
+};
+
+void plat_Accelerate (moveinfo_t *moveinfo)
+{
+	// are we decelerating?
+	if (moveinfo->remaining_distance <= moveinfo->decel_distance)
+	{
+		if (moveinfo->remaining_distance < moveinfo->decel_distance)
+		{
+			if (moveinfo->next_speed)
+			{
+				moveinfo->current_speed = moveinfo->next_speed;
+				moveinfo->next_speed = 0;
+				return;
+			}
+			if (moveinfo->current_speed > moveinfo->decel)
+				moveinfo->current_speed -= moveinfo->decel;
+		}
+		return;
+	}
+
+	// are we at full speed and need to start decelerating during this move?
+	if (moveinfo->current_speed == moveinfo->move_speed)
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
+		{
+			float	p1_distance;
+			float	p2_distance;
+			float	distance;
+
+			p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+			p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
+			distance = p1_distance + p2_distance;
+			moveinfo->current_speed = moveinfo->move_speed;
+			moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+			return;
+		}
+
+	// are we accelerating?
+	if (moveinfo->current_speed < moveinfo->speed)
+	{
+		float	old_speed;
+		float	p1_distance;
+		float	p1_speed;
+		float	p2_distance;
+		float	distance;
+
+		old_speed = moveinfo->current_speed;
+
+		// figure simple acceleration up to move_speed
+		moveinfo->current_speed += moveinfo->accel;
+		if (moveinfo->current_speed > moveinfo->speed)
+			moveinfo->current_speed = moveinfo->speed;
+
+		// are we accelerating throughout this entire move?
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
+			return;
+
+		// during this move we will accelrate from current_speed to move_speed
+		// and cross over the decel_distance; figure the average speed for the
+		// entire move
+		p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+		p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
+		p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
+		distance = p1_distance + p2_distance;
+		moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
+		moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+		return;
+	}
+
+	// we are at constant velocity (move_speed)
+	return;
+};
+
+void Think_AccelMove (edict_t *ent)
+{
+	ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
+
+	if (ent->moveinfo.current_speed == 0)		// starting or blocked
+		plat_CalcAcceleratedMove(&ent->moveinfo);
+
+	plat_Accelerate (&ent->moveinfo);
+
+	// will the entire move complete on next frame?
+	if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
+	{
+		Move_Final (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
+	ent->nextthink = level.time + FRAMETIME;
+	ent->think = Think_AccelMove;
+}
+
+
+void plat_go_down (edict_t *ent);
+
+void plat_hit_top (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_TOP;
+
+	ent->think = plat_go_down;
+	ent->nextthink = level.time + 3;
+}
+
+void plat_hit_bottom (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_BOTTOM;
+}
+
+void plat_go_down (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_DOWN;
+	Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
+}
+
+void plat_go_up (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_UP;
+	Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
+}
+
+void plat_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->moveinfo.state == STATE_UP)
+		plat_go_down (self);
+	else if (self->moveinfo.state == STATE_DOWN)
+		plat_go_up (self);
+}
+
+
+void Use_Plat (edict_t *ent, edict_t *, edict_t *)
+{ 
+	if (ent->think)
+		return;		// already down
+	plat_go_down (ent);
+}
+
+
+void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+		
+	if (other->health <= 0)
+		return;
+
+	ent = ent->enemy;	// now point at the plat, not the trigger
+	if (ent->moveinfo.state == STATE_BOTTOM)
+		plat_go_up (ent);
+	else if (ent->moveinfo.state == STATE_TOP)
+		ent->nextthink = level.time + 1;	// the player is still on the plat, so delay going down
+}
+
+void plat_spawn_inside_trigger (edict_t *ent)
+{
+	edict_t	*trigger;
+	vec3_t	tmin, tmax;
+
+//
+// middle trigger
+//	
+	trigger = G_Spawn();
+	trigger->touch = Touch_Plat_Center;
+	trigger->movetype = MOVETYPE_NONE;
+	trigger->solid = SOLID_TRIGGER;
+	trigger->enemy = ent;
+
+    trigger->classname = "plat trigger";
+	
+	tmin[0] = ent->mins[0] + 25;
+	tmin[1] = ent->mins[1] + 25;
+	tmin[2] = ent->mins[2];
+
+	tmax[0] = ent->maxs[0] - 25;
+	tmax[1] = ent->maxs[1] - 25;
+	tmax[2] = ent->maxs[2] + 8;
+
+	tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
+
+	if (ent->spawnflags & PLAT_LOW_TRIGGER)
+		tmax[2] = tmin[2] + 8;
+	
+	if (tmax[0] - tmin[0] <= 0)
+	{
+		tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
+		tmax[0] = tmin[0] + 1;
+	}
+	if (tmax[1] - tmin[1] <= 0)
+	{
+		tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
+		tmax[1] = tmin[1] + 1;
+	}
+	
+	VectorCopy (tmin, trigger->mins);
+	VectorCopy (tmax, trigger->maxs);
+
+	gi.linkentity (trigger);
+}
+
+
+/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
+speed	default 150
+
+Plats are always drawn in the extended position, so they will light correctly.
+
+If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
+
+"speed"	overrides default 200.
+"accel" overrides default 500
+"lip"	overrides default 8 pixel lip
+
+If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
+
+Set "sounds" to one of the following:
+1) base fast
+2) chain slow
+*/
+void SP_func_plat (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+	ent->solid = SOLID_BSP;
+	ent->movetype = MOVETYPE_PUSH;
+
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = plat_blocked;
+
+	if (!ent->speed)
+		ent->speed = 20;
+	else
+		ent->speed *= 0.1;
+
+	if (!ent->accel)
+		ent->accel = 5;
+	else
+		ent->accel *= 0.1;
+
+	if (!ent->decel)
+		ent->decel = 5;
+	else
+		ent->decel *= 0.1;
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!st.lip)
+		st.lip = 8;
+
+	// pos1 is the top position, pos2 is the bottom
+	VectorCopy (ent->s.origin, ent->pos1);
+	VectorCopy (ent->s.origin, ent->pos2);
+	if (st.height)
+		ent->pos2[2] -= st.height;
+	else
+		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+
+	ent->use = Use_Plat;
+
+	plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
+
+	if (ent->targetname)
+	{
+		ent->moveinfo.state = STATE_UP;
+	}
+	else
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		gi.linkentity (ent);
+		ent->moveinfo.state = STATE_BOTTOM;
+	}
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
+}
+
+//====================================================================
+
+/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
+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.
+
+"speed" determines how fast it moves; default value is 100.
+"dmg"	damage to inflict when blocked (2 default)
+
+REVERSE will cause the it to rotate in the opposite direction.
+STOP mean it will stop moving instead of pushing entities
+*/
+
+void rotating_blocked (edict_t *self, edict_t *other)
+{
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!VectorCompare (self->avelocity, vec3_origin))
+	{
+		self->s.sound = 0;
+		VectorClear (self->avelocity);
+		self->touch = NULL;
+	}
+	else
+	{
+		self->s.sound = self->moveinfo.sound_middle;
+		VectorScale (self->movedir, self->speed, self->avelocity);
+		if (self->spawnflags & 16)
+			self->touch = rotating_touch;
+	}
+}
+
+void SP_func_rotating (edict_t *ent)
+{
+	ent->solid = SOLID_BSP;
+	if (ent->spawnflags & 32)
+		ent->movetype = MOVETYPE_STOP;
+	else
+		ent->movetype = MOVETYPE_PUSH;
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & 4)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & 8)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & 2)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+//	ent->moveinfo.sound_middle = "doors/hydro1.wav";
+
+	ent->use = rotating_use;
+	if (ent->dmg)
+		ent->blocked = rotating_blocked;
+
+	if (ent->spawnflags & 1)
+		ent->use (ent, NULL, NULL);
+
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 128)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+BUTTONS
+
+======================================================================
+*/
+
+/*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.
+
+"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
+"sounds"
+1) silent
+2) steam metal
+3) wooden clunk
+4) metallic click
+5) in-out
+*/
+
+void button_done (edict_t *self)
+{
+	self->moveinfo.state = STATE_BOTTOM;
+	self->s.effects &= ~EF_ANIM23;
+	self->s.effects |= EF_ANIM01;
+}
+
+void button_return (edict_t *self)
+{
+	self->moveinfo.state = STATE_DOWN;
+
+	Move_Calc (self, self->moveinfo.start_origin, button_done);
+
+	self->s.frame = 0;
+
+	if (self->health)
+		self->takedamage = DAMAGE_YES;
+}
+
+void button_wait (edict_t *self)
+{
+	self->moveinfo.state = STATE_TOP;
+	self->s.effects &= ~EF_ANIM01;
+	self->s.effects |= EF_ANIM23;
+
+	G_UseTargets (self, self->activator);
+	self->s.frame = 1;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->nextthink = level.time + self->moveinfo.wait;
+		self->think = button_return;
+	}
+}
+
+void button_fire (edict_t *self)
+{
+	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		return;
+
+	self->moveinfo.state = STATE_UP;
+	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
+		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+	Move_Calc (self, self->moveinfo.end_origin, button_wait);
+}
+
+void button_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	button_fire (self);
+}
+
+void button_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (other->health <= 0)
+		return;
+
+	self->activator = other;
+	button_fire (self);
+}
+
+void button_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->activator = attacker;
+	self->health = self->max_health;
+	self->takedamage = DAMAGE_NO;
+	button_fire (self);
+}
+
+void SP_func_button (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+	float	dist;
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_STOP;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	if (ent->sounds != 1)
+		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
+	
+	if (!ent->speed)
+		ent->speed = 40;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 4;
+
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
+
+	ent->use = button_use;
+	ent->s.effects |= EF_ANIM01;
+
+	if (ent->health)
+	{
+		ent->max_health = ent->health;
+		ent->die = button_killed;
+		ent->takedamage = DAMAGE_YES;
+	}
+	else if (! ent->targetname)
+		ent->touch = button_touch;
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+DOORS
+
+  spawn a trigger surrounding the entire team unless it is
+  allready targeted by another
+
+======================================================================
+*/
+
+/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
+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
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"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)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void door_use_areaportals (edict_t *self, qboolean open)
+{
+	edict_t	*t = NULL;
+
+	if (!self->target)
+		return;
+
+	while ((t = G_Find (t, FOFS(targetname), self->target)))
+	{
+		if (cistrcmp(t->classname, "func_areaportal") == 0)
+		{
+			gi.SetAreaPortalState (t->style, open);
+		}
+	}
+}
+
+void door_go_down (edict_t *self);
+
+void door_hit_top (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_TOP;
+	if (self->spawnflags & DOOR_TOGGLE)
+		return;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->think = door_go_down;
+		self->nextthink = level.time + self->moveinfo.wait;
+	}
+}
+
+void door_hit_bottom (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_BOTTOM;
+	door_use_areaportals (self, false);
+}
+
+void door_go_down (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	if (self->max_health)
+	{
+		self->takedamage = DAMAGE_YES;
+		self->health = self->max_health;
+	}
+	
+	self->moveinfo.state = STATE_DOWN;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_bottom);
+}
+
+void door_go_up (edict_t *self, edict_t *activator)
+{
+	if (self->moveinfo.state == STATE_UP)
+		return;		// already going up
+
+	if (self->moveinfo.state == STATE_TOP)
+	{	// reset top wait time
+		if (self->moveinfo.wait >= 0)
+			self->nextthink = level.time + self->moveinfo.wait;
+		return;
+	}
+	
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	self->moveinfo.state = STATE_UP;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_top);
+
+	G_UseTargets (self, activator);
+	door_use_areaportals (self, true);
+}
+
+void door_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*ent;
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;
+
+	if (self->spawnflags & DOOR_TOGGLE)
+	{
+		if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		{
+			// trigger all paired doors
+			for (ent = self ; ent ; ent = ent->teamchain)
+			{
+				ent->message = NULL;
+				ent->touch = NULL;
+				door_go_down (ent);
+			}
+			return;
+		}
+	}
+	
+	// trigger all paired doors
+	for (ent = self ; ent ; ent = ent->teamchain)
+	{
+		ent->message = NULL;
+		ent->touch = NULL;
+		door_go_up (ent, activator);
+	}
+};
+
+void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->health <= 0)
+		return;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client))
+		return;
+
+	if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 1.0;
+
+	door_use (self->owner, other, other);
+}
+
+void Think_CalcMoveSpeed (edict_t *self)
+{
+	edict_t	*ent;
+	float	min;
+	float	time;
+	float	newspeed;
+	float	ratio;
+	float	dist;
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;		// only the team master does this
+
+	// find the smallest distance any member of the team will be moving
+	min = fabs(self->moveinfo.distance);
+	for (ent = self->teamchain; ent; ent = ent->teamchain)
+	{
+		dist = fabs(ent->moveinfo.distance);
+		if (dist < min)
+			min = dist;
+	}
+
+	time = min / self->moveinfo.speed;
+
+	// adjust speeds so they will all complete at the same time
+	for (ent = self; ent; ent = ent->teamchain)
+	{
+		newspeed = fabs(ent->moveinfo.distance) / time;
+		ratio = newspeed / ent->moveinfo.speed;
+		if (ent->moveinfo.accel == ent->moveinfo.speed)
+			ent->moveinfo.accel = newspeed;
+		else
+			ent->moveinfo.accel *= ratio;
+		if (ent->moveinfo.decel == ent->moveinfo.speed)
+			ent->moveinfo.decel = newspeed;
+		else
+			ent->moveinfo.decel *= ratio;
+		ent->moveinfo.speed = newspeed;
+	}
+}
+
+void Think_SpawnDoorTrigger (edict_t *ent)
+{
+	edict_t		*other;
+	vec3_t		mins, maxs;
+
+	if (ent->flags & FL_TEAMSLAVE)
+		return;		// only the team leader spawns a trigger
+
+	VectorCopy (ent->absmin, mins);
+	VectorCopy (ent->absmax, maxs);
+
+	for (other = ent->teamchain ; other ; other=other->teamchain)
+	{
+		AddPointToBounds (other->absmin, mins, maxs);
+		AddPointToBounds (other->absmax, mins, maxs);
+	}
+
+	// expand 
+	mins[0] -= 60;
+	mins[1] -= 60;
+	maxs[0] += 60;
+	maxs[1] += 60;
+
+	other = G_Spawn ();
+	VectorCopy (mins, other->mins);
+	VectorCopy (maxs, other->maxs);
+	other->owner = ent;
+	other->solid = SOLID_TRIGGER;
+	other->movetype = MOVETYPE_NONE;
+	other->touch = Touch_DoorTrigger;
+
+    other->classname = "door trigger";
+
+	gi.linkentity (other);
+
+	if (ent->spawnflags & DOOR_START_OPEN)
+		door_use_areaportals (ent, true);
+
+	Think_CalcMoveSpeed (ent);
+}
+
+void door_blocked  (edict_t *self, edict_t *other)
+{
+	edict_t	*ent;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->spawnflags & DOOR_CRUSHER)
+		return;
+
+
+// if a door has a negative wait, it would never come back if blocked,
+// so let it just squash the object to death real fast
+	if (self->moveinfo.wait >= 0)
+	{
+		if (self->moveinfo.state == STATE_DOWN)
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_up (ent, ent->activator);
+		}
+		else
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_down (ent);
+		}
+	}
+}
+
+void door_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	edict_t	*ent;
+
+	for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+	{
+		ent->health = ent->max_health;
+		ent->takedamage = DAMAGE_NO;
+	}
+	door_use (self->teammaster, attacker, attacker);
+}
+
+void door_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 5.0;
+
+	if (!other->bot_info) gi.centerprintf (other, "%s", self->message);
+	gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+}
+
+void SP_func_door (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+	
+	if (!ent->speed)
+		ent->speed = 100;
+	if (deathmatch->value)
+		ent->speed *= 2;
+
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 8;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	// calculate second position
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.origin, ent->pos1);
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+}
+
+
+/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
+TOGGLE causes the door to 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
+
+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.
+
+"distance" is how many degrees the door will be rotated.
+"speed" determines how fast the door moves; default value is 100.
+
+REVERSE will cause the door to rotate in the opposite direction.
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"speed"		movement speed (100 default)
+"wait"		wait before returning (3 default, -1 = never return)
+"dmg"		damage to inflict when blocked (2 default)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void SP_func_door_rotating (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & DOOR_X_AXIS)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & DOOR_Y_AXIS)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & DOOR_REVERSE)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!st.distance)
+	{
+		gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
+		st.distance = 90;
+	}
+
+	VectorCopy (ent->s.angles, ent->pos1);
+	VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
+	ent->moveinfo.distance = st.distance;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.angles);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.angles, ent->pos1);
+		VectorNegate (ent->movedir, ent->movedir);
+	}
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	
+	if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
+	VectorCopy (ent->pos1, ent->moveinfo.start_angles);
+	VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
+	VectorCopy (ent->pos2, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+}
+
+
+/*QUAKED func_water (0 .5 .8) ? START_OPEN
+func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
+
+START_OPEN causes the water to move to its destination when spawned and operate in reverse.
+
+"angle"		determines the opening direction (up or down only)
+"speed"		movement speed (25 default)
+"wait"		wait before returning (-1 default, -1 = TOGGLE)
+"lip"		lip remaining at end of move (0 default)
+"sounds"	(yes, these need to be changed)
+0)	no sound
+1)	water
+2)	lava
+*/
+
+void SP_func_water (edict_t *self)
+{
+	vec3_t	abs_movedir;
+
+	G_SetMovedir (self->s.angles, self->movedir);
+	self->movetype = MOVETYPE_PUSH;
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	switch (self->sounds)
+	{
+		default:
+			break;
+
+		case 1: // water
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+
+		case 2: // lava
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+	}
+
+	// calculate second position
+	VectorCopy (self->s.origin, self->pos1);
+	abs_movedir[0] = fabs(self->movedir[0]);
+	abs_movedir[1] = fabs(self->movedir[1]);
+	abs_movedir[2] = fabs(self->movedir[2]);
+	self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
+	VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
+
+	// if it starts open, switch the positions
+	if (self->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (self->pos2, self->s.origin);
+		VectorCopy (self->pos1, self->pos2);
+		VectorCopy (self->s.origin, self->pos1);
+	}
+
+	VectorCopy (self->pos1, self->moveinfo.start_origin);
+	VectorCopy (self->s.angles, self->moveinfo.start_angles);
+	VectorCopy (self->pos2, self->moveinfo.end_origin);
+	VectorCopy (self->s.angles, self->moveinfo.end_angles);
+
+	self->moveinfo.state = STATE_BOTTOM;
+
+	if (!self->speed)
+		self->speed = 25;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
+
+	if (!self->wait)
+		self->wait = -1;
+	self->moveinfo.wait = self->wait;
+
+	self->use = door_use;
+
+	if (self->wait == -1)
+		self->spawnflags |= DOOR_TOGGLE;
+
+	self->classname = "func_door";
+
+	gi.linkentity (self);
+}
+
+
+#define TRAIN_START_ON		1
+#define TRAIN_TOGGLE		2
+#define TRAIN_BLOCK_STOPS	4
+
+/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+Trains are moving platforms that players can ride.
+The targets origin specifies the min point of the train at each corner.
+The train spawns at the first target it is pointing at.
+If the train is the target of a button or trigger, it will not begin moving until activated.
+speed	default 100
+dmg		default	2
+noise	looping sound to play when the train is in motion
+
+*/
+void train_next (edict_t *self);
+
+void train_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+
+	if (!self->dmg)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void train_wait (edict_t *self)
+{
+	if (self->target_ent->pathtarget)
+	{
+		char	*savetarget;
+		edict_t	*ent;
+
+		ent = self->target_ent;
+		savetarget = ent->target;
+		ent->target = ent->pathtarget;
+		G_UseTargets (ent, self->activator);
+		ent->target = savetarget;
+
+		// make sure we didn't get killed by a killtarget
+		if (!self->inuse)
+			return;
+	}
+
+	if (self->moveinfo.wait)
+	{
+		if (self->moveinfo.wait > 0)
+		{
+			self->nextthink = level.time + self->moveinfo.wait;
+			self->think = train_next;
+		}
+		else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
+		{
+			train_next (self);
+			self->spawnflags &= ~TRAIN_START_ON;
+			VectorClear (self->velocity);
+			self->nextthink = 0;
+		}
+
+		if (!(self->flags & FL_TEAMSLAVE))
+		{
+			if (self->moveinfo.sound_end)
+				gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+			self->s.sound = 0;
+		}
+	}
+	else
+	{
+		train_next (self);
+	}
+	
+}
+
+void train_next (edict_t *self)
+{
+	edict_t		*ent;
+	vec3_t		dest;
+	qboolean	first;
+
+	first = true;
+again:
+	if (!self->target)
+	{
+//		gi.dprintf ("train_next: no next target\n");
+		return;
+	}
+
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_next: bad target %s\n", self->target);
+		return;
+	}
+
+	self->target = ent->target;
+
+	// check for a teleport path_corner
+	if (ent->spawnflags & 1)
+	{
+		if (!first)
+		{
+			gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
+			return;
+		}
+		first = false;
+		VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+		VectorCopy (self->s.origin, self->s.old_origin);
+		gi.linkentity (self);
+		goto again;
+	}
+
+	self->moveinfo.wait = ent->wait;
+	self->target_ent = ent;
+
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+}
+
+void train_resume (edict_t *self)
+{
+	edict_t	*ent;
+	vec3_t	dest;
+
+	ent = self->target_ent;
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+}
+
+void func_train_find (edict_t *self)
+{
+	edict_t *ent;
+
+	if (!self->target)
+	{
+		gi.dprintf ("train_find: no target\n");
+		return;
+	}
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_find: target %s not found\n", self->target);
+		return;
+	}
+	self->target = ent->target;
+
+	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+	gi.linkentity (self);
+
+	// if not triggered, start immediately
+	if (!self->targetname)
+		self->spawnflags |= TRAIN_START_ON;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		self->nextthink = level.time + FRAMETIME;
+		self->think = train_next;
+		self->activator = self;
+	}
+}
+
+void train_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		if (!(self->spawnflags & TRAIN_TOGGLE))
+			return;
+		self->spawnflags &= ~TRAIN_START_ON;
+		VectorClear (self->velocity);
+		self->nextthink = 0;
+	}
+	else
+	{
+		if (self->target_ent)
+			train_resume(self);
+		else
+			train_next(self);
+	}
+}
+
+void SP_func_train (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+
+	VectorClear (self->s.angles);
+	self->blocked = train_blocked;
+	if (self->spawnflags & TRAIN_BLOCK_STOPS)
+		self->dmg = 0;
+	else
+	{
+		if (!self->dmg)
+			self->dmg = 100;
+	}
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	if (st.noise)
+		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
+
+	if (!self->speed)
+		self->speed = 100;
+
+	self->moveinfo.speed = self->speed;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
+
+	self->use = train_use;
+
+	gi.linkentity (self);
+
+	if (self->target)
+	{
+		// start trains on the second frame, to make sure their targets have had
+		// a chance to spawn
+		self->nextthink = level.time + FRAMETIME;
+		self->think = func_train_find;
+	}
+	else
+	{
+		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
+	}
+}
+
+
+/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+*/
+void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *)
+{
+	edict_t *target;
+
+	if (self->movetarget->nextthink)
+	{
+//		gi.dprintf("elevator busy\n");
+		return;
+	}
+
+	if (!other->pathtarget)
+	{
+		gi.dprintf("elevator used with no pathtarget\n");
+		return;
+	}
+
+	target = G_PickTarget (other->pathtarget);
+	if (!target)
+	{
+		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
+		return;
+	}
+
+	self->movetarget->target_ent = target;
+	train_resume (self->movetarget);
+}
+
+void trigger_elevator_init (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("trigger_elevator has no target\n");
+		return;
+	}
+	self->movetarget = G_PickTarget (self->target);
+	if (!self->movetarget)
+	{
+		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
+		return;
+	}
+	if (strcmp(self->movetarget->classname, "func_train") != 0)
+	{
+		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
+		return;
+	}
+
+	self->use = trigger_elevator_use;
+	self->svflags = SVF_NOCLIENT;
+
+}
+
+void SP_trigger_elevator (edict_t *self)
+{
+	self->think = trigger_elevator_init;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+"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)
+
+"delay"			delay before first firing when turned on, default is 0
+
+"pausetime"		additional delay used only the very first time
+				and only if spawned with START_ON
+
+These can used but not touched.
+*/
+void func_timer_think (edict_t *self)
+{
+	G_UseTargets (self, self->activator);
+	self->nextthink = level.time + self->wait + crandom() * self->random;
+}
+
+void func_timer_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	// if on, turn it off
+	if (self->nextthink)
+	{
+		self->nextthink = 0;
+		return;
+	}
+
+	// turn it on
+	if (self->delay)
+		self->nextthink = level.time + self->delay;
+	else
+		func_timer_think (self);
+}
+
+void SP_func_timer (edict_t *self)
+{
+	if (!self->wait)
+		self->wait = 1.0;
+
+	self->use = func_timer_use;
+	self->think = func_timer_think;
+
+	if (self->random >= self->wait)
+	{
+		self->random = self->wait - FRAMETIME;
+		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
+	}
+
+	if (self->spawnflags & 1)
+	{
+		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
+		self->activator = self;
+	}
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
+Conveyors are stationary brushes that move what's on them.
+The brush should be have a surface with at least one current content enabled.
+speed	default 100
+*/
+
+void func_conveyor_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & 1)
+	{
+		self->speed = 0;
+		self->spawnflags &= ~1;
+	}
+	else
+	{
+		self->speed = self->count;
+		self->spawnflags |= 1;
+	}
+
+	if (!(self->spawnflags & 2))
+		self->count = 0;
+}
+
+void SP_func_conveyor (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 100;
+
+	if (!(self->spawnflags & 1))
+	{
+		self->count = self->speed;
+		self->speed = 0;
+	}
+
+	self->use = func_conveyor_use;
+
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
+A secret door.  Slide back and then to the side.
+
+open_once		doors never closes
+1st_left		1st move is left of arrow
+1st_down		1st move is down from arrow
+always_shoot	door is shootebale even if targeted
+
+"angle"		determines the direction
+"dmg"		damage to inflic when blocked (default 2)
+"wait"		how long to hold in the open position (default 5, -1 means hold)
+*/
+
+#define SECRET_ALWAYS_SHOOT	1
+#define SECRET_1ST_LEFT		2
+#define SECRET_1ST_DOWN		4
+
+void door_secret_move1 (edict_t *self);
+void door_secret_move2 (edict_t *self);
+void door_secret_move3 (edict_t *self);
+void door_secret_move4 (edict_t *self);
+void door_secret_move5 (edict_t *self);
+void door_secret_move6 (edict_t *self);
+void door_secret_done (edict_t *self);
+
+void door_secret_use (edict_t *self, edict_t *, edict_t *)
+{
+	// make sure we're not already moving
+	if (!VectorCompare(self->s.origin, vec3_origin))
+		return;
+
+	Move_Calc (self, self->pos1, door_secret_move1);
+	door_use_areaportals (self, true);
+}
+
+void door_secret_move1 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move2;
+}
+
+void door_secret_move2 (edict_t *self)
+{
+	Move_Calc (self, self->pos2, door_secret_move3);
+}
+
+void door_secret_move3 (edict_t *self)
+{
+	if (self->wait == -1)
+		return;
+	self->nextthink = level.time + self->wait;
+	self->think = door_secret_move4;
+}
+
+void door_secret_move4 (edict_t *self)
+{
+	Move_Calc (self, self->pos1, door_secret_move5);
+}
+
+void door_secret_move5 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move6;
+}
+
+void door_secret_move6 (edict_t *self)
+{
+	Move_Calc (self, vec3_origin, door_secret_done);
+}
+
+void door_secret_done (edict_t *self)
+{
+	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		self->health = 0;
+		self->takedamage = DAMAGE_YES;
+	}
+	door_use_areaportals (self, false);
+}
+
+void door_secret_blocked  (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void door_secret_die (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	door_secret_use (self, attacker, attacker);
+}
+
+void SP_func_door_secret (edict_t *ent)
+{
+	vec3_t	forward, right, up;
+	float	side;
+	float	width;
+	float	length;
+
+	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_secret_blocked;
+	ent->use = door_secret_use;
+
+	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		ent->health = 0;
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_secret_die;
+	}
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!ent->wait)
+		ent->wait = 5;
+
+	ent->moveinfo.accel =
+	ent->moveinfo.decel =
+	ent->moveinfo.speed = 50;
+
+	// calculate positions
+	AngleVectors (ent->s.angles, forward, right, up);
+	VectorClear (ent->s.angles);
+	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		width = fabs(DotProduct(up, ent->size));
+	else
+		width = fabs(DotProduct(right, ent->size));
+	length = fabs(DotProduct(forward, ent->size));
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
+	else
+		VectorMA (ent->s.origin, side * width, right, ent->pos1);
+	VectorMA (ent->pos1, length, forward, ent->pos2);
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->classname = "func_door";
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED func_killbox (1 0 0) ?
+Kills everything inside when fired, irrespective of protection.
+*/
+void use_killbox (edict_t *self, edict_t *, edict_t *)
+{
+	KillBox (self);
+}
+
+void SP_func_killbox (edict_t *ent)
+{
+	gi.setmodel (ent, ent->model);
+	ent->use = use_killbox;
+	ent->svflags = SVF_NOCLIENT;
+}
+
--- /dev/null
+++ b/crbot/g_items.c
@@ -1,0 +1,2422 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+qboolean        Pickup_Weapon (edict_t *ent, edict_t *other);
+void            Use_Weapon (edict_t *ent, gitem_t *inv);
+void            Drop_Weapon (edict_t *ent, gitem_t *inv);
+
+void Weapon_Blaster (edict_t *ent);
+void Weapon_Shotgun (edict_t *ent);
+void Weapon_SuperShotgun (edict_t *ent);
+void Weapon_Machinegun (edict_t *ent);
+void Weapon_Chaingun (edict_t *ent);
+void Weapon_HyperBlaster (edict_t *ent);
+void Weapon_RocketLauncher (edict_t *ent);
+void Weapon_Grenade (edict_t *ent);
+void Weapon_GrenadeLauncher (edict_t *ent);
+void Weapon_Railgun (edict_t *ent);
+void Weapon_BFG (edict_t *ent);
+
+gitem_armor_t jacketarmor_info  = { 25,  50, .30, .00, ARMOR_JACKET};
+gitem_armor_t combatarmor_info  = { 50, 100, .60, .30, ARMOR_COMBAT};
+gitem_armor_t bodyarmor_info    = {100, 200, .80, .60, ARMOR_BODY};
+
+static int      jacket_armor_index;
+static int      combat_armor_index;
+static int      body_armor_index;
+static int      power_screen_index;
+static int      power_shield_index;
+
+#define HEALTH_IGNORE_MAX       1
+#define HEALTH_TIMED            2
+
+void Use_Quad (edict_t *ent, gitem_t *item);
+static int      quad_drop_timeout_hack;
+
+//======================================================================
+
+/*
+===============
+GetItemByIndex
+===============
+*/
+gitem_t *GetItemByIndex (int index)
+{
+        if (index == 0 || index >= game.num_items)
+                return NULL;
+
+        return &itemlist[index];
+}
+
+
+/*
+===============
+FindItemByClassname
+
+===============
+*/
+gitem_t *FindItemByClassname (char *classname)
+{
+        int             i;
+        gitem_t *it;
+
+        it = itemlist;
+        for (i=0 ; i<game.num_items ; i++, it++)
+        {
+                if (!it->classname)
+                        continue;
+                if (!cistrcmp(it->classname, classname))
+                        return it;
+        }
+
+        return NULL;
+}
+
+/*
+===============
+FindItem
+
+===============
+*/
+gitem_t *FindItem (char *pickup_name)
+{
+        int             i;
+        gitem_t *it;
+
+        it = itemlist;
+        for (i=0 ; i<game.num_items ; i++, it++)
+        {
+                if (!it->pickup_name)
+                        continue;
+                if (!cistrcmp(it->pickup_name, pickup_name))
+                        return it;
+        }
+
+        return NULL;
+}
+
+//======================================================================
+
+void DoRespawn (edict_t *ent)
+{
+        if (ent->team)
+        {
+                edict_t *master;
+                int     count;
+                int choice;
+
+                master = ent->teammaster;
+
+//ZOID
+//in ctf, when we are weapons stay, only the master of a team of weapons
+//is spawned
+                if (ctf->value &&
+                        ((int)dmflags->value & DF_WEAPONS_STAY) &&
+                        master->item && (master->item->flags & IT_WEAPON))
+                        ent = master;
+                else {
+//ZOID
+
+                        for (count = 0, ent = master; ent; ent = ent->chain, count++)
+                                ;
+
+                        choice = rand() % count;
+
+                        for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
+                                ;
+                }
+        }
+
+        ent->svflags &= ~SVF_NOCLIENT;
+        ent->solid = SOLID_TRIGGER;
+        gi.linkentity (ent);
+
+        // send an effect
+        ent->s.event = EV_ITEM_RESPAWN;
+}
+
+void SetRespawn (edict_t *ent, float delay)
+{
+        ent->flags |= FL_RESPAWN;
+        ent->svflags |= SVF_NOCLIENT;
+        ent->solid = SOLID_NOT;
+        ent->nextthink = level.time + delay;
+        ent->think = DoRespawn;
+        gi.linkentity (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Powerup (edict_t *ent, edict_t *other)
+{
+        int             quantity;
+
+        quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+        if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
+                return false;
+
+        if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+                return false;
+
+        other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+        if (deathmatch->value)
+        {
+                if (!(ent->spawnflags & DROPPED_ITEM) )
+                        SetRespawn (ent, ent->item->quantity);
+                if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+                {
+                        if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))
+                                quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME;
+                        ent->item->use (other, ent->item);
+                }
+        }
+
+        return true;
+}
+
+void Drop_General (edict_t *ent, gitem_t *item)
+{
+        Drop_Item (ent, item);
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other)
+{
+        if (!deathmatch->value)
+                other->max_health += 1;
+
+        if (other->health < other->max_health)
+                other->health = other->max_health;
+
+        if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (ent, ent->item->quantity);
+
+        return true;
+}
+
+qboolean Pickup_AncientHead (edict_t *ent, edict_t *other)
+{
+        other->max_health += 2;
+
+        if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (ent, ent->item->quantity);
+
+        return true;
+}
+
+qboolean Pickup_Bandolier (edict_t *ent, edict_t *other)
+{
+        gitem_t *item;
+        int             index;
+
+        if (other->client->pers.max_bullets < 250)
+                other->client->pers.max_bullets = 250;
+        if (other->client->pers.max_shells < 150)
+                other->client->pers.max_shells = 150;
+        if (other->client->pers.max_cells < 250)
+                other->client->pers.max_cells = 250;
+        if (other->client->pers.max_slugs < 75)
+                other->client->pers.max_slugs = 75;
+
+        item = FindItem("Bullets");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+                        other->client->pers.inventory[index] = other->client->pers.max_bullets;
+        }
+
+        item = FindItem("Shells");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+                        other->client->pers.inventory[index] = other->client->pers.max_shells;
+        }
+
+        if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (ent, ent->item->quantity);
+
+        return true;
+}
+
+qboolean Pickup_Pack (edict_t *ent, edict_t *other)
+{
+        gitem_t *item;
+        int             index;
+
+        if (other->client->pers.max_bullets < 300)
+                other->client->pers.max_bullets = 300;
+        if (other->client->pers.max_shells < 200)
+                other->client->pers.max_shells = 200;
+        if (other->client->pers.max_rockets < 100)
+                other->client->pers.max_rockets = 100;
+        if (other->client->pers.max_grenades < 100)
+                other->client->pers.max_grenades = 100;
+        if (other->client->pers.max_cells < 300)
+                other->client->pers.max_cells = 300;
+        if (other->client->pers.max_slugs < 100)
+                other->client->pers.max_slugs = 100;
+
+        item = FindItem("Bullets");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+                        other->client->pers.inventory[index] = other->client->pers.max_bullets;
+        }
+
+        item = FindItem("Shells");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+                        other->client->pers.inventory[index] = other->client->pers.max_shells;
+        }
+
+        item = FindItem("Cells");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_cells)
+                        other->client->pers.inventory[index] = other->client->pers.max_cells;
+        }
+
+        item = FindItem("Grenades");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_grenades)
+                        other->client->pers.inventory[index] = other->client->pers.max_grenades;
+        }
+
+        item = FindItem("Rockets");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_rockets)
+                        other->client->pers.inventory[index] = other->client->pers.max_rockets;
+        }
+
+        item = FindItem("Slugs");
+        if (item)
+        {
+                index = ITEM_INDEX(item);
+                other->client->pers.inventory[index] += item->quantity;
+                if (other->client->pers.inventory[index] > other->client->pers.max_slugs)
+                        other->client->pers.inventory[index] = other->client->pers.max_slugs;
+        }
+
+        if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (ent, ent->item->quantity);
+
+        return true;
+}
+
+//======================================================================
+
+void Use_Quad (edict_t *ent, gitem_t *item)
+{
+        int             timeout;
+
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+
+        if (quad_drop_timeout_hack)
+        {
+                timeout = quad_drop_timeout_hack;
+                quad_drop_timeout_hack = 0;
+        }
+        else
+        {
+                timeout = 300;
+        }
+
+        if (ent->client->quad_framenum > level.framenum)
+                ent->client->quad_framenum += timeout;
+        else
+                ent->client->quad_framenum = level.framenum + timeout;
+
+        gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void Use_Breather (edict_t *ent, gitem_t *item)
+{
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+
+        if (ent->client->breather_framenum > level.framenum)
+                ent->client->breather_framenum += 300;
+        else
+                ent->client->breather_framenum = level.framenum + 300;
+
+//      gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void Use_Envirosuit (edict_t *ent, gitem_t *item)
+{
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+
+        if (ent->client->enviro_framenum > level.framenum)
+                ent->client->enviro_framenum += 300;
+        else
+                ent->client->enviro_framenum = level.framenum + 300;
+
+//      gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void    Use_Invulnerability (edict_t *ent, gitem_t *item)
+{
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+
+        if (ent->client->invincible_framenum > level.framenum)
+                ent->client->invincible_framenum += 300;
+        else
+                ent->client->invincible_framenum = level.framenum + 300;
+
+        gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void    Use_Silencer (edict_t *ent, gitem_t *item)
+{
+        ent->client->pers.inventory[ITEM_INDEX(item)]--;
+        ValidateSelectedItem (ent);
+        ent->client->silencer_shots += 30;
+
+//      gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+qboolean Pickup_Key (edict_t *ent, edict_t *other)
+{
+        if (coop->value)
+        {
+                if (strcmp(ent->classname, "key_power_cube") == 0)
+                {
+                        if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8))
+                                return false;
+                        other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+                        other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8);
+                }
+                else
+                {
+                        if (other->client->pers.inventory[ITEM_INDEX(ent->item)])
+                                return false;
+                        other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
+                }
+                return true;
+        }
+        other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+        return true;
+}
+
+//======================================================================
+
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count)
+{
+        int                     index;
+        int                     max;
+
+        if (!ent->client)
+                return false;
+
+        if (item->tag == AMMO_BULLETS)
+                max = ent->client->pers.max_bullets;
+        else if (item->tag == AMMO_SHELLS)
+                max = ent->client->pers.max_shells;
+        else if (item->tag == AMMO_ROCKETS)
+                max = ent->client->pers.max_rockets;
+        else if (item->tag == AMMO_GRENADES)
+                max = ent->client->pers.max_grenades;
+        else if (item->tag == AMMO_CELLS)
+                max = ent->client->pers.max_cells;
+        else if (item->tag == AMMO_SLUGS)
+                max = ent->client->pers.max_slugs;
+        else
+                return false;
+
+        index = ITEM_INDEX(item);
+
+        if (ent->client->pers.inventory[index] == max)
+                return false;
+
+        ent->client->pers.inventory[index] += count;
+
+        if (ent->client->pers.inventory[index] > max)
+                ent->client->pers.inventory[index] = max;
+
+        return true;
+}
+
+qboolean Pickup_Ammo (edict_t *ent, edict_t *other)
+{
+        int                     oldcount;
+        int                     count;
+        qboolean        weapon;
+
+        weapon = (ent->item->flags & IT_WEAPON);
+        if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+                count = 1000;
+        else if (ent->count)
+                count = ent->count;
+        else
+                count = ent->item->quantity;
+
+        oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+        if (!Add_Ammo (other, ent->item, count))
+                return false;
+
+        if (weapon && !oldcount)
+        {
+                if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) )
+                        other->client->newweapon = ent->item;
+        }
+
+        if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
+                SetRespawn (ent, 30);
+        return true;
+}
+
+void Drop_Ammo (edict_t *ent, gitem_t *item)
+{
+        edict_t *dropped;
+        int             index;
+
+        index = ITEM_INDEX(item);
+        dropped = Drop_Item (ent, item);
+        if (ent->client->pers.inventory[index] >= item->quantity)
+                dropped->count = item->quantity;
+        else
+                dropped->count = ent->client->pers.inventory[index];
+        ent->client->pers.inventory[index] -= dropped->count;
+        ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+void MegaHealth_think (edict_t *self)
+{
+        if (self->owner->health > self->owner->max_health
+//ZOID
+                && !CTFHasRegeneration(self->owner)
+//ZOID
+                )
+        {
+                self->nextthink = level.time + 1;
+                self->owner->health -= 1;
+                return;
+        }
+
+        if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (self, 20);
+        else
+                G_FreeEdict (self);
+}
+
+qboolean Pickup_Health (edict_t *ent, edict_t *other)
+{
+        if (!(ent->style & HEALTH_IGNORE_MAX))
+                if (other->health >= other->max_health)
+                        return false;
+
+//ZOID
+        if (other->health >= 250 && ent->count > 25)
+                return false;
+//ZOID
+
+        other->health += ent->count;
+
+//ZOID
+        if (other->health > 250 && ent->count > 25)
+                other->health = 250;
+//ZOID
+
+        if (ent->count == 2)
+                ent->item->pickup_sound = "items/s_health.wav";
+        else if (ent->count == 10)
+                ent->item->pickup_sound = "items/n_health.wav";
+        else if (ent->count == 25)
+                ent->item->pickup_sound = "items/l_health.wav";
+        else // (ent->count == 100)
+                ent->item->pickup_sound = "items/m_health.wav";
+
+        if (!(ent->style & HEALTH_IGNORE_MAX))
+        {
+                if (other->health > other->max_health)
+                        other->health = other->max_health;
+        }
+
+//ZOID
+        if ((ent->style & HEALTH_TIMED)
+                && !CTFHasRegeneration(other)
+//ZOID
+        )
+        {
+                ent->think = MegaHealth_think;
+                ent->nextthink = level.time + 5;
+                ent->owner = other;
+                ent->flags |= FL_RESPAWN;
+                ent->svflags |= SVF_NOCLIENT;
+                ent->solid = SOLID_NOT;
+        }
+        else
+        {
+                if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                        SetRespawn (ent, 30);
+        }
+
+        return true;
+}
+
+//======================================================================
+
+int ArmorIndex (edict_t *ent)
+{
+        if (!ent->client)
+                return 0;
+
+        if (ent->client->pers.inventory[jacket_armor_index] > 0)
+                return jacket_armor_index;
+
+        if (ent->client->pers.inventory[combat_armor_index] > 0)
+                return combat_armor_index;
+
+        if (ent->client->pers.inventory[body_armor_index] > 0)
+                return body_armor_index;
+
+        return 0;
+}
+
+qboolean Pickup_Armor (edict_t *ent, edict_t *other)
+{
+        int                             old_armor_index;
+        gitem_armor_t   *oldinfo;
+        gitem_armor_t   *newinfo;
+        int                             newcount;
+        float                   salvage;
+        int                             salvagecount;
+
+        // get info on new armor
+        newinfo = (gitem_armor_t *)ent->item->info;
+
+        old_armor_index = ArmorIndex (other);
+
+        // handle armor shards specially
+        if (ent->item->tag == ARMOR_SHARD)
+        {
+                if (!old_armor_index)
+                        other->client->pers.inventory[jacket_armor_index] = 2;
+                else
+                        other->client->pers.inventory[old_armor_index] += 2;
+        }
+
+        // if player has no armor, just use it
+        else if (!old_armor_index)
+        {
+                other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count;
+        }
+
+        // use the better armor
+        else
+        {
+                // get info on old armor
+                if (old_armor_index == jacket_armor_index)
+                        oldinfo = &jacketarmor_info;
+                else if (old_armor_index == combat_armor_index)
+                        oldinfo = &combatarmor_info;
+                else // (old_armor_index == body_armor_index)
+                        oldinfo = &bodyarmor_info;
+
+                if (newinfo->normal_protection > oldinfo->normal_protection)
+                {
+                        // calc new armor values
+                        salvage = oldinfo->normal_protection / newinfo->normal_protection;
+                        salvagecount = salvage * other->client->pers.inventory[old_armor_index];
+                        newcount = newinfo->base_count + salvagecount;
+                        if (newcount > newinfo->max_count)
+                                newcount = newinfo->max_count;
+
+                        // zero count of old armor so it goes away
+                        other->client->pers.inventory[old_armor_index] = 0;
+
+                        // change armor to new item with computed value
+                        other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
+                }
+                else
+                {
+                        // calc new armor values
+                        salvage = newinfo->normal_protection / oldinfo->normal_protection;
+                        salvagecount = salvage * newinfo->base_count;
+                        newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
+                        if (newcount > oldinfo->max_count)
+                                newcount = oldinfo->max_count;
+
+                        // if we're already maxed out then we don't need the new armor
+                        if (other->client->pers.inventory[old_armor_index] >= newcount)
+                                return false;
+
+                        // update current armor value
+                        other->client->pers.inventory[old_armor_index] = newcount;
+                }
+        }
+
+        if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+                SetRespawn (ent, 20);
+
+        return true;
+}
+
+//======================================================================
+
+int PowerArmorType (edict_t *ent)
+{
+        if (!ent->client)
+                return POWER_ARMOR_NONE;
+
+        if (!(ent->flags & FL_POWER_ARMOR))
+                return POWER_ARMOR_NONE;
+
+        if (ent->client->pers.inventory[power_shield_index] > 0)
+                return POWER_ARMOR_SHIELD;
+
+        if (ent->client->pers.inventory[power_screen_index] > 0)
+                return POWER_ARMOR_SCREEN;
+
+        return POWER_ARMOR_NONE;
+}
+
+void Use_PowerArmor (edict_t *ent, gitem_t *)
+{
+        int             index;
+
+        if (ent->flags & FL_POWER_ARMOR)
+        {
+                ent->flags &= ~FL_POWER_ARMOR;
+                gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+        }
+        else
+        {
+                index = ITEM_INDEX(FindItem("cells"));
+                if (!ent->client->pers.inventory[index])
+                {
+                        if (!ent->bot_info) gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n");
+                        return;
+                }
+                ent->flags |= FL_POWER_ARMOR;
+                gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
+        }
+}
+
+qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other)
+{
+        int             quantity;
+
+        quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+        other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+        if (deathmatch->value)
+        {
+                if (!(ent->spawnflags & DROPPED_ITEM) )
+                        SetRespawn (ent, ent->item->quantity);
+                // auto-use for DM only if we didn't already have one
+                if (!quantity)
+                        ent->item->use (other, ent->item);
+        }
+
+        return true;
+}
+
+void Drop_PowerArmor (edict_t *ent, gitem_t *item)
+{
+        if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
+                Use_PowerArmor (ent, item);
+        Drop_General (ent, item);
+}
+
+//======================================================================
+
+/*
+===============
+Touch_Item
+===============
+*/
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+        qboolean        taken;
+
+        if (!other->client)
+                return;
+        if (other->health < 1)
+                return;         // dead people can't pickup
+        if (!ent->item->pickup)
+                return;         // not a grabbable item?
+
+        taken = ent->item->pickup(ent, other);
+
+        if (taken)
+        {
+                // flash the screen
+                other->client->bonus_alpha = 0.25;      
+
+                // show icon and name on status bar
+                other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
+                other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item);
+                other->client->pickup_msg_time = level.time + 3.0;
+
+                // change selected item
+                if (ent->item->use)
+                        other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);
+
+                gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
+        }
+
+        if (!(ent->spawnflags & ITEM_TARGETS_USED))
+        {
+                G_UseTargets (ent, other);
+                ent->spawnflags |= ITEM_TARGETS_USED;
+        }
+
+        if (!taken)
+                return;
+
+        if (!((coop->value) &&  (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
+        {
+                if (ent->flags & FL_RESPAWN)
+                        ent->flags &= ~FL_RESPAWN;
+                else
+                        G_FreeEdict (ent);
+        }
+}
+
+//======================================================================
+
+static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+        if (other == ent->owner)
+                return;
+
+        Touch_Item (ent, other, plane, surf);
+}
+
+static void drop_make_touchable (edict_t *ent)
+{
+        ent->touch = Touch_Item;
+        if (deathmatch->value)
+        {
+                ent->nextthink = level.time + 29;
+                ent->think = G_FreeEdict;
+        }
+}
+
+edict_t *Drop_Item (edict_t *ent, gitem_t *item)
+{
+        edict_t *dropped;
+        vec3_t  forward, right;
+        vec3_t  offset;
+
+        dropped = G_Spawn();
+
+        dropped->classname = item->classname;
+        dropped->item = item;
+        dropped->spawnflags = DROPPED_ITEM;
+        dropped->s.effects = item->world_model_flags;
+        dropped->s.renderfx = RF_GLOW;
+        VectorSet (dropped->mins, -15, -15, -15);
+        VectorSet (dropped->maxs, 15, 15, 15);
+        gi.setmodel (dropped, dropped->item->world_model);
+        dropped->solid = SOLID_TRIGGER;
+        dropped->movetype = MOVETYPE_TOSS;  
+        dropped->touch = drop_temp_touch;
+        dropped->owner = ent;
+
+        if (ent->client)
+        {
+                trace_t trace;
+
+                AngleVectors (ent->client->v_angle, forward, right, NULL);
+                VectorSet(offset, 24, 0, -16);
+                G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin);
+                trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs,
+                        dropped->s.origin, ent, CONTENTS_SOLID);
+                VectorCopy (trace.endpos, dropped->s.origin);
+        }
+        else
+        {
+                AngleVectors (ent->s.angles, forward, right, NULL);
+                VectorCopy (ent->s.origin, dropped->s.origin);
+        }
+
+        VectorScale (forward, 100, dropped->velocity);
+        dropped->velocity[2] = 300;
+
+        dropped->think = drop_make_touchable;
+        dropped->nextthink = level.time + 1;
+
+        gi.linkentity (dropped);
+
+        return dropped;
+}
+
+void Use_Item (edict_t *ent, edict_t *, edict_t *)
+{
+        ent->svflags &= ~SVF_NOCLIENT;
+        ent->use = NULL;
+
+        if (ent->spawnflags & ITEM_NO_TOUCH)
+        {
+                ent->solid = SOLID_BBOX;
+                ent->touch = NULL;
+        }
+        else
+        {
+                ent->solid = SOLID_TRIGGER;
+                ent->touch = Touch_Item;
+        }
+
+        gi.linkentity (ent);
+}
+
+//======================================================================
+
+/*
+================
+droptofloor
+================
+*/
+void droptofloor (edict_t *ent)
+{
+        trace_t         tr;
+        vec3_t          dest;
+        float           *v;
+
+        v = tv(-15,-15,-15);
+        VectorCopy (v, ent->mins);
+        v = tv(15,15,15);
+        VectorCopy (v, ent->maxs);
+
+        if (ent->model)
+                gi.setmodel (ent, ent->model);
+        else
+                gi.setmodel (ent, ent->item->world_model);
+        ent->solid = SOLID_TRIGGER;
+        ent->movetype = MOVETYPE_TOSS;  
+        ent->touch = Touch_Item;
+
+        v = tv(0,0,-128);
+        VectorAdd (ent->s.origin, v, dest);
+
+        tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+        if (tr.startsolid)
+        {
+                gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
+                G_FreeEdict (ent);
+                return;
+        }
+
+        VectorCopy (tr.endpos, ent->s.origin);
+
+        if (ent->team)
+        {
+                ent->flags &= ~FL_TEAMSLAVE;
+                ent->chain = ent->teamchain;
+                ent->teamchain = NULL;
+
+                ent->svflags |= SVF_NOCLIENT;
+                ent->solid = SOLID_NOT;
+                if (ent == ent->teammaster)
+                {
+                        ent->nextthink = level.time + FRAMETIME;
+                        ent->think = DoRespawn;
+                }
+        }
+
+        if (ent->spawnflags & ITEM_NO_TOUCH)
+        {
+                ent->solid = SOLID_BBOX;
+                ent->touch = NULL;
+                ent->s.effects &= ~EF_ROTATE;
+                ent->s.renderfx &= ~RF_GLOW;
+        }
+
+        if (ent->spawnflags & ITEM_TRIGGER_SPAWN)
+        {
+                ent->svflags |= SVF_NOCLIENT;
+                ent->solid = SOLID_NOT;
+                ent->use = Use_Item;
+        }
+
+        gi.linkentity (ent);
+}
+
+
+/*
+===============
+PrecacheItem
+
+Precaches all data needed for a given item.
+This will be called for each item spawned in a level,
+and for each item in each client's inventory.
+===============
+*/
+void PrecacheItem (gitem_t *it)
+{
+        char    *s, *start;
+        char    data[MAX_QPATH];
+        int             len;
+        gitem_t *ammo;
+
+        if (!it)
+                return;
+
+        if (it->pickup_sound)
+                gi.soundindex (it->pickup_sound);
+        if (it->world_model)
+                gi.modelindex (it->world_model);
+        if (it->view_model)
+                gi.modelindex (it->view_model);
+        if (it->icon)
+                gi.imageindex (it->icon);
+
+        // parse everything for its ammo
+        if (it->ammo && it->ammo[0])
+        {
+                ammo = FindItem (it->ammo);
+                if (ammo != it)
+                        PrecacheItem (ammo);
+        }
+
+        // parse the space seperated precache string for other items
+        s = it->precaches;
+        if (!s || !s[0])
+                return;
+
+        while (*s)
+        {
+                start = s;
+                while (*s && *s != ' ')
+                        s++;
+
+                len = s-start;
+                if (len >= MAX_QPATH || len < 5)
+                        gi.error ("PrecacheItem: %s has bad precache string", it->classname);
+                memcpy (data, start, len);
+                data[len] = 0;
+                if (*s)
+                        s++;
+
+                // determine type based on extension
+                if (!strcmp(data+len-3, "md2"))
+                        gi.modelindex (data);
+                else if (!strcmp(data+len-3, "sp2"))
+                        gi.modelindex (data);
+                else if (!strcmp(data+len-3, "wav"))
+                        gi.soundindex (data);
+                if (!strcmp(data+len-3, "pcx"))
+                        gi.imageindex (data);
+        }
+}
+
+/*
+============
+SpawnItem
+
+Sets the clipping size and plants the object on the floor.
+
+Items can't be immediately dropped to floor, because they might
+be on an entity that hasn't spawned yet.
+============
+*/
+void SpawnItem (edict_t *ent, gitem_t *item)
+{
+        PrecacheItem (item);
+
+        if (ent->spawnflags)
+        {
+                if (strcmp(ent->classname, "key_power_cube") != 0)
+                {
+                        ent->spawnflags = 0;
+                        gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin));
+                }
+        }
+
+        // some items will be prevented in deathmatch
+        if (deathmatch->value)
+        {
+                if ( (int)dmflags->value & DF_NO_ARMOR )
+                {
+                        if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor)
+                        {
+                                G_FreeEdict (ent);
+                                return;
+                        }
+                }
+                if ( (int)dmflags->value & DF_NO_ITEMS )
+                {
+                        if (item->pickup == Pickup_Powerup)
+                        {
+                                G_FreeEdict (ent);
+                                return;
+                        }
+                }
+                if ( (int)dmflags->value & DF_NO_HEALTH )
+                {
+                        if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead)
+                        {
+                                G_FreeEdict (ent);
+                                return;
+                        }
+                }
+                if ( (int)dmflags->value & DF_INFINITE_AMMO )
+                {
+                        if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) )
+                        {
+                                G_FreeEdict (ent);
+                                return;
+                        }
+                }
+        }
+
+        if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0))
+        {
+                ent->spawnflags |= (1 << (8 + level.power_cubes));
+                level.power_cubes++;
+        }
+
+        // don't let them drop items that stay in a coop game
+        if ((coop->value) && (item->flags & IT_STAY_COOP))
+        {
+                item->drop = NULL;
+        }
+
+//ZOID
+//Don't spawn the flags unless enabled
+        if (!ctf->value &&
+                (strcmp(ent->classname, "item_flag_team1") == 0 ||
+                strcmp(ent->classname, "item_flag_team2") == 0)) {
+                G_FreeEdict(ent);
+                return;
+        }
+//ZOID
+
+        ent->item = item;
+        ent->nextthink = level.time + 2 * FRAMETIME;    // items start after other solids
+        ent->think = droptofloor;
+        ent->s.effects = item->world_model_flags;
+        ent->s.renderfx = RF_GLOW;
+        if (ent->model)
+                gi.modelindex (ent->model);
+
+//ZOID
+//flags are server animated and have special handling
+        if (strcmp(ent->classname, "item_flag_team1") == 0 ||
+                strcmp(ent->classname, "item_flag_team2") == 0) {
+                ent->think = CTFFlagSetup;
+        }
+//ZOID
+
+}
+
+//======================================================================
+
+gitem_t itemlist[] = 
+{
+        {
+                NULL
+        },      // leave index 0 alone
+
+        //
+        // ARMOR
+        //
+
+/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_armor_body", 
+                Pickup_Armor,
+                NULL,
+                NULL,
+                NULL,
+                "misc/ar1_pkup.wav",
+                "models/items/armor/body/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_bodyarmor",
+/* pickup */    "Body Armor",
+/* width */             3,
+                0,
+                NULL,
+                IT_ARMOR,
+				0,
+                &bodyarmor_info,
+                ARMOR_BODY,
+/* precache */ ""
+        },
+
+/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_armor_combat", 
+                Pickup_Armor,
+                NULL,
+                NULL,
+                NULL,
+                "misc/ar1_pkup.wav",
+                "models/items/armor/combat/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_combatarmor",
+/* pickup */    "Combat Armor",
+/* width */             3,
+                0,
+                NULL,
+                IT_ARMOR,
+				0,
+                &combatarmor_info,
+                ARMOR_COMBAT,
+/* precache */ ""
+        },
+
+/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_armor_jacket", 
+                Pickup_Armor,
+                NULL,
+                NULL,
+                NULL,
+                "misc/ar1_pkup.wav",
+                "models/items/armor/jacket/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_jacketarmor",
+/* pickup */    "Jacket Armor",
+/* width */             3,
+                0,
+                NULL,
+                IT_ARMOR,
+				0,
+                &jacketarmor_info,
+                ARMOR_JACKET,
+/* precache */ ""
+        },
+
+/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_armor_shard", 
+                Pickup_Armor,
+                NULL,
+                NULL,
+                NULL,
+                "misc/ar2_pkup.wav",
+                "models/items/armor/shard/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_jacketarmor",
+/* pickup */    "Armor Shard",
+/* width */             3,
+                0,
+                NULL,
+                IT_ARMOR,
+				0,
+                NULL,
+                ARMOR_SHARD,
+/* precache */ ""
+        },
+
+
+/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_power_screen", 
+                Pickup_PowerArmor,
+                Use_PowerArmor,
+                Drop_PowerArmor,
+                NULL,
+                "misc/ar3_pkup.wav",
+                "models/items/armor/screen/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_powerscreen",
+/* pickup */    "Power Screen",
+/* width */             0,
+                60,
+                NULL,
+                IT_ARMOR,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_power_shield",
+                Pickup_PowerArmor,
+                Use_PowerArmor,
+                Drop_PowerArmor,
+                NULL,
+                "misc/ar3_pkup.wav",
+                "models/items/armor/shield/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_powershield",
+/* pickup */    "Power Shield",
+/* width */             0,
+                60,
+                NULL,
+                IT_ARMOR,
+				0,
+                NULL,
+                0,
+/* precache */ "misc/power2.wav misc/power1.wav"
+        },
+
+
+        //
+        // WEAPONS 
+        //
+
+/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16)
+always owned, never in the world
+*/
+        {
+                "weapon_grapple", 
+                NULL,
+                Use_Weapon,
+                NULL,
+                CTFWeapon_Grapple,
+                "misc/w_pkup.wav",
+                NULL, 0,
+                "models/weapons/grapple/tris.md2",
+/* icon */              "w_grapple",
+/* pickup */    "Grapple",
+                0,
+                0,
+                NULL,
+                IT_WEAPON,
+				WEAP_GRAPPLE,
+                NULL,
+                0,
+/* precache */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav"
+        },
+
+/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+always owned, never in the world
+*/
+        {
+                "weapon_blaster", 
+                NULL,
+                Use_Weapon,
+                NULL,
+                Weapon_Blaster,
+                "misc/w_pkup.wav",
+                NULL, 0,
+                "models/weapons/v_blast/tris.md2",
+/* icon */              "w_blaster",
+/* pickup */    "Blaster",
+                0,
+                0,
+                NULL,
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_BLASTER,
+                NULL,
+                0,
+/* precache */ "weapons/blastf1a.wav misc/lasfly.wav"
+        },
+
+        
+/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_shotgun", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_Shotgun,
+                "misc/w_pkup.wav",
+                "models/weapons/g_shotg/tris.md2", EF_ROTATE,
+                "models/weapons/v_shotg/tris.md2",
+/* icon */              "w_shotgun",
+/* pickup */    "Shotgun",
+                0,
+                1,
+                "Shells",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_SHOTGUN,
+                NULL,
+                0,
+/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav"
+        },
+
+/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_supershotgun", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_SuperShotgun,
+                "misc/w_pkup.wav",
+                "models/weapons/g_shotg2/tris.md2", EF_ROTATE,
+                "models/weapons/v_shotg2/tris.md2",
+/* icon */              "w_sshotgun",
+/* pickup */    "Super Shotgun",
+                0,
+                2,
+                "Shells",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_SUPERSHOTGUN,
+                NULL,
+                0,
+/* precache */ "weapons/sshotf1b.wav"
+        },
+
+/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_machinegun", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_Machinegun,
+                "misc/w_pkup.wav",
+                "models/weapons/g_machn/tris.md2", EF_ROTATE,
+                "models/weapons/v_machn/tris.md2",
+/* icon */              "w_machinegun",
+/* pickup */    "Machinegun",
+                0,
+                1,
+                "Bullets",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_MACHINEGUN,
+                NULL,
+                0,
+/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
+        },
+
+/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_chaingun", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_Chaingun,
+                "misc/w_pkup.wav",
+                "models/weapons/g_chain/tris.md2", EF_ROTATE,
+                "models/weapons/v_chain/tris.md2",
+/* icon */              "w_chaingun",
+/* pickup */    "Chaingun",
+                0,
+                1,
+                "Bullets",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_CHAINGUN,
+                NULL,
+                0,
+/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
+        },
+
+/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_grenades",
+                Pickup_Ammo,
+                Use_Weapon,
+                Drop_Ammo,
+                Weapon_Grenade,
+                "misc/am_pkup.wav",
+                "models/items/ammo/grenades/medium/tris.md2", 0,
+                "models/weapons/v_handgr/tris.md2",
+/* icon */              "a_grenades",
+/* pickup */    "Grenades",
+/* width */             3,
+                5,
+                "grenades",
+                IT_AMMO|IT_WEAPON,
+				WEAP_GRENADES,
+                NULL,
+                AMMO_GRENADES,
+/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+        },
+
+/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_grenadelauncher",
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_GrenadeLauncher,
+                "misc/w_pkup.wav",
+                "models/weapons/g_launch/tris.md2", EF_ROTATE,
+                "models/weapons/v_launch/tris.md2",
+/* icon */              "w_glauncher",
+/* pickup */    "Grenade Launcher",
+                0,
+                1,
+                "Grenades",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_GRENADELAUNCHER,
+                NULL,
+                0,
+/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
+        },
+
+/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_rocketlauncher",
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_RocketLauncher,
+                "misc/w_pkup.wav",
+                "models/weapons/g_rocket/tris.md2", EF_ROTATE,
+                "models/weapons/v_rocket/tris.md2",
+/* icon */              "w_rlauncher",
+/* pickup */    "Rocket Launcher",
+                0,
+                1,
+                "Rockets",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_ROCKETLAUNCHER,
+                NULL,
+                0,
+/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
+        },
+
+/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_hyperblaster", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_HyperBlaster,
+                "misc/w_pkup.wav",
+                "models/weapons/g_hyperb/tris.md2", EF_ROTATE,
+                "models/weapons/v_hyperb/tris.md2",
+/* icon */              "w_hyperblaster",
+/* pickup */    "HyperBlaster",
+                0,
+                1,
+                "Cells",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_HYPERBLASTER,
+                NULL,
+                0,
+/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
+        },
+
+/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_railgun", 
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_Railgun,
+                "misc/w_pkup.wav",
+                "models/weapons/g_rail/tris.md2", EF_ROTATE,
+                "models/weapons/v_rail/tris.md2",
+/* icon */              "w_railgun",
+/* pickup */    "Railgun",
+                0,
+                1,
+                "Slugs",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_RAILGUN,
+                NULL,
+                0,
+/* precache */ "weapons/rg_hum.wav"
+        },
+
+/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "weapon_bfg",
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_BFG,
+                "misc/w_pkup.wav",
+                "models/weapons/g_bfg/tris.md2", EF_ROTATE,
+                "models/weapons/v_bfg/tris.md2",
+/* icon */              "w_bfg",
+/* pickup */    "BFG10K",
+                0,
+                50,
+                "Cells",
+                IT_WEAPON|IT_STAY_COOP,
+				WEAP_BFG,
+                NULL,
+                0,
+/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
+        },
+
+//ZOID
+/*QUAKED weapon_laser (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+/*
+        {
+                "weapon_laser",
+                Pickup_Weapon,
+                Use_Weapon,
+                Drop_Weapon,
+                Weapon_Laser,
+                "misc/w_pkup.wav",
+                "models/weapons/g_laser/tris.md2", EF_ROTATE,
+                "models/weapons/v_laser/tris.md2",
+		"w_bfg",	// icon
+		"Flashlight Laser",	// pickup
+                0,
+                1,
+                "Cells",
+                IT_WEAPON,
+		0,
+                NULL,
+                0,
+		""	// precache
+        },
+*/
+
+        //
+        // AMMO ITEMS
+        //
+
+/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_shells",
+                Pickup_Ammo,
+                NULL,
+                Drop_Ammo,
+                NULL,
+                "misc/am_pkup.wav",
+                "models/items/ammo/shells/medium/tris.md2", 0,
+                NULL,
+/* icon */              "a_shells",
+/* pickup */    "Shells",
+/* width */             3,
+                10,
+                NULL,
+                IT_AMMO,
+				0,
+                NULL,
+                AMMO_SHELLS,
+/* precache */ ""
+        },
+
+/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_bullets",
+                Pickup_Ammo,
+                NULL,
+                Drop_Ammo,
+                NULL,
+                "misc/am_pkup.wav",
+                "models/items/ammo/bullets/medium/tris.md2", 0,
+                NULL,
+/* icon */              "a_bullets",
+/* pickup */    "Bullets",
+/* width */             3,
+                50,
+                NULL,
+                IT_AMMO,
+				0,
+                NULL,
+                AMMO_BULLETS,
+/* precache */ ""
+        },
+
+/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_cells",
+                Pickup_Ammo,
+                NULL,
+                Drop_Ammo,
+                NULL,
+                "misc/am_pkup.wav",
+                "models/items/ammo/cells/medium/tris.md2", 0,
+                NULL,
+/* icon */              "a_cells",
+/* pickup */    "Cells",
+/* width */             3,
+                50,
+                NULL,
+                IT_AMMO,
+				0,
+                NULL,
+                AMMO_CELLS,
+/* precache */ ""
+        },
+
+/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_rockets",
+                Pickup_Ammo,
+                NULL,
+                Drop_Ammo,
+                NULL,
+                "misc/am_pkup.wav",
+                "models/items/ammo/rockets/medium/tris.md2", 0,
+                NULL,
+/* icon */              "a_rockets",
+/* pickup */    "Rockets",
+/* width */             3,
+                5,
+                NULL,
+                IT_AMMO,
+				0,
+                NULL,
+                AMMO_ROCKETS,
+/* precache */ ""
+        },
+
+/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "ammo_slugs",
+                Pickup_Ammo,
+                NULL,
+                Drop_Ammo,
+                NULL,
+                "misc/am_pkup.wav",
+                "models/items/ammo/slugs/medium/tris.md2", 0,
+                NULL,
+/* icon */              "a_slugs",
+/* pickup */    "Slugs",
+/* width */             3,
+                10,
+                NULL,
+                IT_AMMO,
+				0,
+                NULL,
+                AMMO_SLUGS,
+/* precache */ ""
+        },
+
+
+        //
+        // POWERUP ITEMS
+        //
+/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_quad", 
+                Pickup_Powerup,
+                Use_Quad,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/quaddama/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_quad",
+/* pickup */    "Quad Damage",
+/* width */             2,
+                60,
+                NULL,
+                IT_POWERUP,
+				0,
+                NULL,
+                0,
+/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav"
+        },
+
+/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_invulnerability",
+                Pickup_Powerup,
+                Use_Invulnerability,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/invulner/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_invulnerability",
+/* pickup */    "Invulnerability",
+/* width */             2,
+                300,
+                NULL,
+                IT_POWERUP,
+				0,
+                NULL,
+                0,
+/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav"
+        },
+
+/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_silencer",
+                Pickup_Powerup,
+                Use_Silencer,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/silencer/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_silencer",
+/* pickup */    "Silencer",
+/* width */             2,
+                60,
+                NULL,
+                IT_POWERUP,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_breather",
+                Pickup_Powerup,
+                Use_Breather,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/breather/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_rebreather",
+/* pickup */    "Rebreather",
+/* width */             2,
+                60,
+                NULL,
+                IT_STAY_COOP|IT_POWERUP,
+				0,
+                NULL,
+                0,
+/* precache */ "items/airout.wav"
+        },
+
+/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_enviro",
+                Pickup_Powerup,
+                Use_Envirosuit,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/enviro/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_envirosuit",
+/* pickup */    "Environment Suit",
+/* width */             2,
+                60,
+                NULL,
+                IT_STAY_COOP|IT_POWERUP,
+				0,
+                NULL,
+                0,
+/* precache */ "items/airout.wav"
+        },
+
+/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16)
+Special item that gives +2 to maximum health
+*/
+        {
+                "item_ancient_head",
+                Pickup_AncientHead,
+                NULL,
+                NULL,
+                NULL,
+                "items/pkup.wav",
+                "models/items/c_head/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_fixme",
+/* pickup */    "Ancient Head",
+/* width */             2,
+                60,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16)
+gives +1 to maximum health
+*/
+        {
+                "item_adrenaline",
+                Pickup_Adrenaline,
+                NULL,
+                NULL,
+                NULL,
+                "items/pkup.wav",
+                "models/items/adrenal/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_adrenaline",
+/* pickup */    "Adrenaline",
+/* width */             2,
+                60,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_bandolier",
+                Pickup_Bandolier,
+                NULL,
+                NULL,
+                NULL,
+                "items/pkup.wav",
+                "models/items/band/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "p_bandolier",
+/* pickup */    "Bandolier",
+/* width */             2,
+                60,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+        {
+                "item_pack",
+                Pickup_Pack,
+                NULL,
+                NULL,
+                NULL,
+                "items/pkup.wav",
+                "models/items/pack/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_pack",
+/* pickup */    "Ammo Pack",
+/* width */             2,
+                180,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+        //
+        // KEYS
+        //
+/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for computer centers
+*/
+        {
+                "key_data_cd",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/data_cd/tris.md2", EF_ROTATE,
+                NULL,
+                "k_datacd",
+                "Data CD",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
+warehouse circuits
+*/
+        {
+                "key_power_cube",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/power/tris.md2", EF_ROTATE,
+                NULL,
+                "k_powercube",
+                "Power Cube",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for the entrance of jail3
+*/
+        {
+                "key_pyramid",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/pyramid/tris.md2", EF_ROTATE,
+                NULL,
+                "k_pyramid",
+                "Pyramid Key",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for the city computer
+*/
+        {
+                "key_data_spinner",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/spinner/tris.md2", EF_ROTATE,
+                NULL,
+                "k_dataspin",
+                "Data Spinner",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16)
+security pass for the security level
+*/
+        {
+                "key_pass",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/pass/tris.md2", EF_ROTATE,
+                NULL,
+                "k_security",
+                "Security Pass",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+normal door key - blue
+*/
+        {
+                "key_blue_key",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/key/tris.md2", EF_ROTATE,
+                NULL,
+                "k_bluekey",
+                "Blue Key",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+normal door key - red
+*/
+        {
+                "key_red_key",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/red_key/tris.md2", EF_ROTATE,
+                NULL,
+                "k_redkey",
+                "Red Key",
+                2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
+tank commander's head
+*/
+        {
+                "key_commander_head",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/monsters/commandr/head/tris.md2", EF_GIB,
+                NULL,
+/* icon */              "k_comhead",
+/* pickup */    "Commander's Head",
+/* width */             2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16)
+tank commander's head
+*/
+        {
+                "key_airstrike_target",
+                Pickup_Key,
+                NULL,
+                Drop_General,
+                NULL,
+                "items/pkup.wav",
+                "models/items/keys/target/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "i_airstrike",
+/* pickup */    "Airstrike Marker",
+/* width */             2,
+                0,
+                NULL,
+                IT_STAY_COOP|IT_KEY,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+        {
+                NULL,
+                Pickup_Health,
+                NULL,
+                NULL,
+                NULL,
+                "items/pkup.wav",
+                NULL, 0,
+                NULL,
+/* icon */              "i_health",
+/* pickup */    "Health",
+/* width */             3,
+                0,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ ""
+        },
+
+
+//ZOID
+/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32)
+*/
+        {
+                "item_flag_team1",
+                CTFPickup_Flag,
+                NULL,
+                CTFDrop_Flag, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "ctf/flagtk.wav",
+                "players/male/flag1.md2", EF_FLAG1,
+                NULL,
+/* icon */              "i_ctf1",
+/* pickup */    "Red Flag",
+/* width */             2,
+                0,
+                NULL,
+                0,
+				0,
+                NULL,
+                0,
+/* precache */ "ctf/flagcap.wav"
+        },
+
+/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32)
+*/
+        {
+                "item_flag_team2",
+                CTFPickup_Flag,
+                NULL,
+                CTFDrop_Flag, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "ctf/flagtk.wav",
+                "players/male/flag2.md2", EF_FLAG2,
+                NULL,
+/* icon */              "i_ctf2",
+/* pickup */    "Blue Flag",
+/* width */             2,
+                0,
+                NULL,
+				0,
+                0,
+                NULL,
+                0,
+/* precache */ "ctf/flagcap.wav"
+        },
+
+/* Resistance Tech */
+        {
+                "item_tech1",
+                CTFPickup_Tech,
+                NULL,
+                CTFDrop_Tech, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "items/pkup.wav",
+                "models/ctf/resistance/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "tech1",
+/* pickup */    "Disruptor Shield",
+/* width */             2,
+                0,
+                NULL,
+                IT_TECH,
+				0,
+                NULL,
+                0,
+/* precache */ "ctf/tech1.wav"
+        },
+
+/* Strength Tech */
+        {
+                "item_tech2",
+                CTFPickup_Tech,
+                NULL,
+                CTFDrop_Tech, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "items/pkup.wav",
+                "models/ctf/strength/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "tech2",
+/* pickup */    "Power Amplifier",
+/* width */             2,
+                0,
+                NULL,
+                IT_TECH,
+				0,
+                NULL,
+                0,
+/* precache */ "ctf/tech2.wav ctf/tech2x.wav"
+        },
+
+/* Haste Tech */
+        {
+                "item_tech3",
+                CTFPickup_Tech,
+                NULL,
+                CTFDrop_Tech, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "items/pkup.wav",
+                "models/ctf/haste/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "tech3",
+/* pickup */    "Time Accel",
+/* width */             2,
+                0,
+                NULL,
+                IT_TECH,
+				0,
+                NULL,
+                0,
+/* precache */ "ctf/tech3.wav"
+        },
+
+/* Regeneration Tech */
+        {
+                "item_tech4",
+                CTFPickup_Tech,
+                NULL,
+                CTFDrop_Tech, //Should this be null if we don't want players to drop it manually?
+                NULL,
+                "items/pkup.wav",
+                "models/ctf/regeneration/tris.md2", EF_ROTATE,
+                NULL,
+/* icon */              "tech4",
+/* pickup */    "AutoDoc",
+/* width */             2,
+                0,
+                NULL,
+                IT_TECH,
+				0,
+                NULL,
+                0,
+/* precache */ "ctf/tech4.wav"
+        },
+
+//ZOID
+
+        // end of list marker
+        {NULL}
+};
+
+
+/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health (edict_t *self)
+{
+        if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+        {
+                G_FreeEdict (self);
+                return;
+        }
+
+        self->model = "models/items/healing/medium/tris.md2";
+        self->count = 10;
+        SpawnItem (self, FindItem ("Health"));
+        gi.soundindex ("items/n_health.wav");
+}
+
+/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_small (edict_t *self)
+{
+        if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+        {
+                G_FreeEdict (self);
+                return;
+        }
+
+        self->model = "models/items/healing/stimpack/tris.md2";
+        self->count = 2;
+        SpawnItem (self, FindItem ("Health"));
+        self->style = HEALTH_IGNORE_MAX;
+        gi.soundindex ("items/s_health.wav");
+}
+
+/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_large (edict_t *self)
+{
+        if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+        {
+                G_FreeEdict (self);
+                return;
+        }
+
+        self->model = "models/items/healing/large/tris.md2";
+        self->count = 25;
+        SpawnItem (self, FindItem ("Health"));
+        gi.soundindex ("items/l_health.wav");
+}
+
+/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_mega (edict_t *self)
+{
+        if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+        {
+                G_FreeEdict (self);
+                return;
+        }
+
+        self->model = "models/items/mega_h/tris.md2";
+        self->count = 100;
+        SpawnItem (self, FindItem ("Health"));
+        gi.soundindex ("items/m_health.wav");
+        self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED;
+}
+
+
+void InitItems (void)
+{
+        game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1;
+}
+
+
+
+/*
+===============
+SetItemNames
+
+Called by worldspawn
+===============
+*/
+void SetItemNames (void)
+{
+        int             i;
+        gitem_t *it;
+
+        for (i=0 ; i<game.num_items ; i++)
+        {
+                it = &itemlist[i];
+                gi.configstring (CS_ITEMS+i, it->pickup_name);
+        }
+
+        jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
+        combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
+        body_armor_index   = ITEM_INDEX(FindItem("Body Armor"));
+        power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
+        power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
+}
--- /dev/null
+++ b/crbot/g_main.c
@@ -1,0 +1,345 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+game_locals_t	game;
+level_locals_t	level;
+game_import_t	gi;
+game_export_t	globals;
+spawn_temp_t	st;
+
+int	sm_meat_index;
+int	snd_fry;
+int meansOfDeath;
+
+edict_t		*g_edicts;
+
+cvar_t	*deathmatch;
+cvar_t	*coop;
+cvar_t	*dmflags;
+cvar_t	*skill;
+cvar_t	*fraglimit;
+cvar_t	*timelimit;
+//ZOID
+cvar_t	*capturelimit;
+//ZOID
+cvar_t	*password;
+cvar_t	*maxclients;
+cvar_t	*maxentities;
+cvar_t	*g_select_empty;
+cvar_t	*dedicated;
+
+cvar_t	*sv_maxvelocity;
+cvar_t	*sv_gravity;
+
+cvar_t	*sv_rollspeed;
+cvar_t	*sv_rollangle;
+cvar_t	*gun_x;
+cvar_t	*gun_y;
+cvar_t	*gun_z;
+
+cvar_t	*run_pitch;
+cvar_t	*run_roll;
+cvar_t	*bob_up;
+cvar_t	*bob_pitch;
+cvar_t	*bob_roll;
+
+cvar_t	*sv_cheats;
+
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
+void ClientThink (edict_t *ent, usercmd_t *cmd);
+qboolean ClientConnect (edict_t *ent, char *userinfo);
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+void ClientDisconnect (edict_t *ent);
+void ClientBegin (edict_t *ent);
+void ClientCommand (edict_t *ent);
+void RunEntity (edict_t *ent);
+void WriteGame (char *filename, qboolean autosave);
+void ReadGame (char *filename);
+void WriteLevel (char *filename);
+void ReadLevel (char *filename);
+void InitGame (void);
+void G_RunFrame (void);
+
+
+//===================================================================
+
+
+void ShutdownGame (void)
+{
+	gi.dprintf ("==== ShutdownGame ====\n");
+
+	gi.FreeTags (TAG_LEVEL);
+	gi.FreeTags (TAG_GAME);
+}
+
+
+/*
+=================
+GetGameAPI
+
+Returns a pointer to the structure with all entry points
+and global variables
+=================
+*/
+game_export_t *GetGameAPI (game_import_t *import)
+{
+	gi = *import;
+
+	globals.apiversion = GAME_API_VERSION;
+	globals.Init = InitGame;
+	globals.Shutdown = ShutdownGame;
+	globals.SpawnEntities = SpawnEntities;
+
+	globals.WriteGame = WriteGame;
+	globals.ReadGame = ReadGame;
+	globals.WriteLevel = WriteLevel;
+	globals.ReadLevel = ReadLevel;
+
+	globals.ClientThink = ClientThink;
+	globals.ClientConnect = ClientConnect;
+	globals.ClientUserinfoChanged = ClientUserinfoChanged;
+	globals.ClientDisconnect = ClientDisconnect;
+	globals.ClientBegin = ClientBegin;
+	globals.ClientCommand = ClientCommand;
+
+	globals.RunFrame = G_RunFrame;
+
+	globals.ServerCommand = ServerCommand;
+
+	globals.edict_size = sizeof(edict_t);
+
+	return &globals;
+}
+
+
+//======================================================================
+
+
+/*
+=================
+ClientEndServerFrames
+=================
+*/
+void ClientEndServerFrames (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	// calc the player views now that all pushing
+	// and damage has been added
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse || !ent->client)
+			continue;
+		ClientEndServerFrame (ent);
+	}
+
+}
+
+/*
+=================
+EndDMLevel
+
+The timelimit or fraglimit has been exceeded
+=================
+*/
+void EndDMLevel (void)
+{
+	edict_t		*ent;
+
+	// stay on same level flag
+	if ((int)dmflags->value & DF_SAME_LEVEL)
+	{
+		ent = G_Spawn ();
+		ent->classname = "target_changelevel";
+		ent->map = level.mapname;
+	}
+    else if (cr_map_cycle()) {
+		ent = G_Spawn ();
+		ent->classname = "target_changelevel";
+		ent->map = next_map_name;
+    }   
+	else if (level.nextmap[0])
+	{	// go to a specific map
+		ent = G_Spawn ();
+		ent->classname = "target_changelevel";
+		ent->map = level.nextmap;
+	}
+	else
+	{	// search for a changeleve
+		ent = G_Find (NULL, FOFS(classname), "target_changelevel");
+		if (!ent)
+		{	// the map designer didn't include a changelevel,
+			// so create a fake ent that goes back to the same level
+			ent = G_Spawn ();
+			ent->classname = "target_changelevel";
+			ent->map = level.mapname;
+		}
+	}
+
+	BeginIntermission (ent);
+}
+
+/*
+=================
+CheckDMRules
+=================
+*/
+void CheckDMRules (void)
+{
+	int			i;
+	gclient_t	*cl;
+
+	if (level.intermissiontime)
+		return;
+
+	if (!deathmatch->value)
+		return;
+
+	if (timelimit->value)
+	{
+		if (level.time >= timelimit->value*60)
+		{
+			gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
+			EndDMLevel ();
+			return;
+		}
+	}
+
+	if (fraglimit->value)
+	{
+//ZOID
+		if (ctf->value) {
+			if (CTFCheckRules()) {
+				EndDMLevel ();
+			}
+		}
+//ZOID
+		for (i=0 ; i<maxclients->value ; i++)
+		{
+			cl = game.clients + i;
+			if (!g_edicts[i+1].inuse)
+				continue;
+
+			if (cl->resp.score >= fraglimit->value)
+			{
+				gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
+				EndDMLevel ();
+				return;
+			}
+		}
+	}
+}
+
+
+/*
+=============
+ExitLevel
+=============
+*/
+void ExitLevel (void)
+{
+	int		i;
+	edict_t	*ent;
+	char	command [256];
+
+    cr_exit_level();
+
+	Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
+	gi.AddCommandString (command);
+	level.changemap = NULL;
+	level.exitintermission = 0;
+	level.intermissiontime = 0;
+	ClientEndServerFrames ();
+
+	// clear some things before going to next level
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse)
+			continue;
+		if (ent->health > ent->client->pers.max_health)
+			ent->health = ent->client->pers.max_health;
+	}
+
+//ZOID
+	CTFInit();
+//ZOID
+
+}
+
+/*
+================
+G_RunFrame
+
+Advances the world by 0.1 seconds
+================
+*/
+void G_RunFrame (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.framenum++;
+	level.time = level.framenum*FRAMETIME;
+
+	// choose a client for monsters to target this frame
+	AI_SetSightClient ();
+
+	// exit intermissions
+
+	if (level.exitintermission)
+	{
+		ExitLevel ();
+		return;
+	}
+
+	//
+	// treat each object in turn
+	// even the world gets a chance to think
+	//
+	ent = &g_edicts[0];
+	for (i=0 ; i<globals.num_edicts ; i++, ent++)
+	{
+		if (!ent->inuse)
+			continue;
+
+		level.current_entity = ent;
+
+		VectorCopy (ent->s.origin, ent->s.old_origin);
+
+		// if the ground entity moved, make sure we are still on it
+		if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount))
+		{
+			ent->groundentity = NULL;
+			if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) )
+			{
+				M_CheckGround (ent);
+			}
+		}
+
+        if (ent->bot_info) {
+          G_RunEntity (ent);
+          continue;
+          }
+        else
+		if (i > 0 && i <= maxclients->value)
+		{
+			ClientBeginServerFrame (ent);
+			continue;
+		}
+
+		G_RunEntity (ent);
+	}
+
+	// see if it is time to end a deathmatch
+	CheckDMRules ();
+
+	// build the playerstate_t structures for all players
+	ClientEndServerFrames ();
+}
+
--- /dev/null
+++ b/crbot/g_misc.c
@@ -1,0 +1,1889 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*QUAKED func_group (0 0 0) ?
+Used to group brushes together just for editor convenience.
+*/
+
+//=====================================================
+
+void Use_Areaportal (edict_t *ent, edict_t *, edict_t *)
+{
+	ent->count ^= 1;		// toggle state
+//	gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count);
+	gi.SetAreaPortalState (ent->style, ent->count);
+}
+
+/*QUAKED func_areaportal (0 0 0) ?
+
+This is a non-visible object that divides the world into
+areas that are seperated when this portal is not activated.
+Usually enclosed in the middle of a door.
+*/
+void SP_func_areaportal (edict_t *ent)
+{
+	ent->use = Use_Areaportal;
+	ent->count = 0;		// allways start closed;
+}
+
+//=====================================================
+
+
+/*
+=================
+Misc functions
+=================
+*/
+void VelocityForDamage (int damage, vec3_t v)
+{
+	v[0] = 100.0 * crandom();
+	v[1] = 100.0 * crandom();
+	v[2] = 200.0 + 100.0 * qrandom();
+
+	if (damage < 50)
+		VectorScale (v, 0.7, v);
+	else 
+		VectorScale (v, 1.2, v);
+}
+
+void ClipGibVelocity (edict_t *ent)
+{
+	if (ent->velocity[0] < -300)
+		ent->velocity[0] = -300;
+	else if (ent->velocity[0] > 300)
+		ent->velocity[0] = 300;
+	if (ent->velocity[1] < -300)
+		ent->velocity[1] = -300;
+	else if (ent->velocity[1] > 300)
+		ent->velocity[1] = 300;
+	if (ent->velocity[2] < 200)
+		ent->velocity[2] = 200;	// always some upwards
+	else if (ent->velocity[2] > 500)
+		ent->velocity[2] = 500;
+}
+
+
+/*
+=================
+gibs
+=================
+*/
+void gib_think (edict_t *self)
+{
+	self->s.frame++;
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->s.frame == 10)
+	{
+		self->think = G_FreeEdict;
+		self->nextthink = level.time + 8 + qrandom()*10;
+	}
+}
+
+void gib_touch (edict_t *self, edict_t *, cplane_t *plane, csurface_t *)
+{
+	vec3_t	normal_angles, right;
+
+	if (!self->groundentity)
+		return;
+
+	self->touch = NULL;
+
+	if (plane)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+		vectoangles (plane->normal, normal_angles);
+		AngleVectors (normal_angles, NULL, right, NULL);
+		vectoangles (right, self->s.angles);
+
+		if (self->s.modelindex == sm_meat_index)
+		{
+			self->s.frame++;
+			self->think = gib_think;
+			self->nextthink = level.time + FRAMETIME;
+		}
+	}
+}
+
+void gib_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowGib (edict_t *self, char *gibname, int damage, int type)
+{
+	edict_t *gib;
+	vec3_t	vd;
+	vec3_t	origin;
+	vec3_t	size;
+	float	vscale;
+
+	gib = G_Spawn();
+
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	gib->s.origin[0] = origin[0] + crandom() * size[0];
+	gib->s.origin[1] = origin[1] + crandom() * size[1];
+	gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+	gi.setmodel (gib, gibname);
+	gib->solid = SOLID_NOT;
+	gib->s.effects |= EF_GIB;
+	gib->flags |= FL_NO_KNOCKBACK;
+	gib->takedamage = DAMAGE_YES;
+	gib->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		gib->movetype = MOVETYPE_TOSS;
+		gib->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		gib->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, gib->velocity);
+	ClipGibVelocity (gib);
+	gib->avelocity[0] = qrandom()*600;
+	gib->avelocity[1] = qrandom()*600;
+	gib->avelocity[2] = qrandom()*600;
+
+	gib->think = G_FreeEdict;
+	gib->nextthink = level.time + 6 + qrandom()*6;
+
+	gi.linkentity (gib);
+}
+
+void ThrowHead (edict_t *self, char *gibname, int damage, int type)
+{
+	vec3_t	vd;
+	float	vscale;
+
+	self->s.skinnum = 0;
+	self->s.frame = 0;
+	VectorClear (self->mins);
+	VectorClear (self->maxs);
+
+	self->s.modelindex2 = 0;
+	gi.setmodel (self, gibname);
+	self->solid = SOLID_NOT;
+	self->s.effects |= EF_GIB;
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+	self->svflags &= ~SVF_MONSTER;
+	self->takedamage = DAMAGE_YES;
+	self->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		self->movetype = MOVETYPE_TOSS;
+		self->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		self->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, self->velocity);
+	ClipGibVelocity (self);
+
+	self->avelocity[YAW] = crandom()*600;
+
+	self->think = G_FreeEdict;
+	self->nextthink = level.time + 6 + qrandom()*6;
+
+	gi.linkentity (self);
+}
+
+
+void ThrowClientHead (edict_t *self, int damage)
+{
+	vec3_t	vd;
+	char	*gibname;
+
+	if (rand()&1)
+	{
+		gibname = "models/objects/gibs/head2/tris.md2";
+		self->s.skinnum = 1;		// second skin is player
+	}
+	else
+	{
+		gibname = "models/objects/gibs/skull/tris.md2";
+		self->s.skinnum = 0;
+	}
+
+	self->s.origin[2] += 32;
+	self->s.frame = 0;
+	gi.setmodel (self, gibname);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 16);
+
+	self->takedamage = DAMAGE_NO;
+	self->solid = SOLID_NOT;
+	self->s.effects = EF_GIB;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+
+	self->movetype = MOVETYPE_BOUNCE;
+	VelocityForDamage (damage, vd);
+	VectorAdd (self->velocity, vd, self->velocity);
+
+	if (self->client)	// bodies in the queue don't have a client anymore
+	{
+		self->client->anim_priority = ANIM_DEATH;
+		self->client->anim_end = self->s.frame;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*
+=================
+debris
+=================
+*/
+void debris_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin)
+{
+	edict_t	*chunk;
+	vec3_t	v;
+
+	chunk = G_Spawn();
+	VectorCopy (origin, chunk->s.origin);
+	gi.setmodel (chunk, modelname);
+	v[0] = 100 * crandom();
+	v[1] = 100 * crandom();
+	v[2] = 100 + 100 * crandom();
+	VectorMA (self->velocity, speed, v, chunk->velocity);
+	chunk->movetype = MOVETYPE_BOUNCE;
+	chunk->solid = SOLID_NOT;
+	chunk->avelocity[0] = qrandom()*600;
+	chunk->avelocity[1] = qrandom()*600;
+	chunk->avelocity[2] = qrandom()*600;
+	chunk->think = G_FreeEdict;
+	chunk->nextthink = level.time + 5 + qrandom()*5;
+	chunk->s.frame = 0;
+	chunk->flags = 0;
+	chunk->classname = "debris";
+	chunk->takedamage = DAMAGE_YES;
+	chunk->die = debris_die;
+	gi.linkentity (chunk);
+}
+
+
+void BecomeExplosion1 (edict_t *self)
+{
+//ZOID
+	//flags are important
+	if (strcmp(self->classname, "item_flag_team1") == 0) {
+		CTFResetFlag(CTF_TEAM1); // this will free self!
+		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
+			CTFTeamName(CTF_TEAM1));
+		return;
+	}
+	if (strcmp(self->classname, "item_flag_team2") == 0) {
+		CTFResetFlag(CTF_TEAM2); // this will free self!
+		gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n",
+			CTFTeamName(CTF_TEAM1));
+		return;
+	}
+	// techs are important too
+	if (self->item && (self->item->flags & IT_TECH)) {
+		CTFRespawnTech(self); // this frees self!
+		return;
+	}
+//ZOID
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+void BecomeExplosion2 (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION2);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
+Target: next path corner
+Pathtarget: gets used when an entity that has
+	this path_corner targeted touches it
+*/
+
+void path_corner_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t		v;
+	edict_t		*next;
+
+	if (other->movetarget != self)
+		return;
+	
+	if (other->enemy)
+		return;
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		G_UseTargets (self, other);
+		self->target = savetarget;
+	}
+
+	if (self->target)
+		next = G_PickTarget(self->target);
+	else
+		next = NULL;
+
+	if ((next) && (next->spawnflags & 1))
+	{
+		VectorCopy (next->s.origin, v);
+		v[2] += next->mins[2];
+		v[2] -= other->mins[2];
+		VectorCopy (v, other->s.origin);
+		next = G_PickTarget(next->target);
+	}
+
+	other->goalentity = other->movetarget = next;
+
+	if (self->wait)
+	{
+		other->monsterinfo.pausetime = level.time + self->wait;
+		other->monsterinfo.stand (other);
+		return;
+	}
+
+	if (!other->movetarget)
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.stand (other);
+	}
+	else
+	{
+		VectorSubtract (other->goalentity->s.origin, other->s.origin, v);
+		other->ideal_yaw = vectoyaw (v);
+	}
+}
+
+void SP_path_corner (edict_t *self)
+{
+	if (!self->targetname)
+	{
+		gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = path_corner_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags |= SVF_NOCLIENT;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
+Makes this the target of a monster and it will head here
+when first activated before going after the activator.  If
+hold is selected, it will stay here.
+*/
+void point_combat_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t	*activator;
+
+	if (other->movetarget != self)
+		return;
+
+	if (self->target)
+	{
+		other->target = self->target;
+		other->goalentity = other->movetarget = G_PickTarget(other->target);
+		if (!other->goalentity)
+		{
+			gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
+			other->movetarget = self;
+		}
+		self->target = NULL;
+	}
+	else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY)))
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.aiflags |= AI_STAND_GROUND;
+		other->monsterinfo.stand (other);
+	}
+
+	if (other->movetarget == self)
+	{
+		other->target = NULL;
+		other->movetarget = NULL;
+		other->goalentity = other->enemy;
+		other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
+	}
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		if (other->enemy && other->enemy->client)
+			activator = other->enemy;
+		else if (other->oldenemy && other->oldenemy->client)
+			activator = other->oldenemy;
+		else if (other->activator && other->activator->client)
+			activator = other->activator;
+		else
+			activator = other;
+		G_UseTargets (self, activator);
+		self->target = savetarget;
+	}
+}
+
+void SP_point_combat (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	self->solid = SOLID_TRIGGER;
+	self->touch = point_combat_touch;
+	VectorSet (self->mins, -8, -8, -16);
+	VectorSet (self->maxs, 8, 8, 16);
+	self->svflags = SVF_NOCLIENT;
+	gi.linkentity (self);
+};
+
+
+/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
+Just for the debugging level.  Don't use
+*/
+static int robotron[4];
+
+void TH_viewthing(edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 7;
+//	ent->s.frame = (ent->s.frame + 1) % 9;
+	ent->nextthink = level.time + FRAMETIME;
+//	return;
+
+	if (ent->spawnflags)
+	{
+		if (ent->s.frame == 0)
+		{
+			ent->spawnflags = (ent->spawnflags + 1) % 4 + 1;
+			ent->s.modelindex = robotron[ent->spawnflags - 1];
+		}
+	}
+}
+
+void SP_viewthing(edict_t *ent)
+{
+	gi.dprintf ("viewthing spawned\n");
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.renderfx = RF_FRAMELERP;
+	VectorSet (ent->mins, -16, -16, -24);
+	VectorSet (ent->maxs, 16, 16, 32);
+//	ent->s.modelindex = gi.modelindex ("models/player_y/tris.md2");
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	gi.linkentity (ent);
+	ent->nextthink = level.time + 0.5;
+	ent->think = TH_viewthing;
+	return;
+}
+
+
+/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for spotlights, etc.
+*/
+void SP_info_null (edict_t *self)
+{
+	G_FreeEdict (self);
+};
+
+
+/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for lightning.
+*/
+void SP_info_notnull (edict_t *self)
+{
+	VectorCopy (self->s.origin, self->absmin);
+	VectorCopy (self->s.origin, self->absmax);
+};
+
+
+/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
+Non-displayed light.
+Default light value is 300.
+Default style is 0.
+If targeted, will toggle between on and off.
+Default _cone value is 10 (used to set size of light for spotlights)
+*/
+
+#define START_OFF	1
+
+static void light_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & START_OFF)
+	{
+		gi.configstring (CS_LIGHTS+self->style, "m");
+		self->spawnflags &= ~START_OFF;
+	}
+	else
+	{
+		gi.configstring (CS_LIGHTS+self->style, "a");
+		self->spawnflags |= START_OFF;
+	}
+}
+
+void SP_light (edict_t *self)
+{
+	// no targeted lights in deathmatch, because they cause global messages
+	if (!self->targetname || deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->style >= 32)
+	{
+		self->use = light_use;
+		if (self->spawnflags & START_OFF)
+			gi.configstring (CS_LIGHTS+self->style, "a");
+		else
+			gi.configstring (CS_LIGHTS+self->style, "m");
+	}
+}
+
+
+/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
+This is just a solid wall if not inhibited
+
+TRIGGER_SPAWN	the wall will not be present until triggered
+				it will then blink in to existance; it will
+				kill anything that was in it's way
+
+TOGGLE			only valid for TRIGGER_SPAWN walls
+				this allows the wall to be turned on and off
+
+START_ON		only valid for TRIGGER_SPAWN walls
+				the wall will initially be present
+*/
+
+void func_wall_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+	{
+		self->solid = SOLID_BSP;
+		self->svflags &= ~SVF_NOCLIENT;
+		KillBox (self);
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+void SP_func_wall (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 8)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 16)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	// just a wall
+	if ((self->spawnflags & 7) == 0)
+	{
+		self->solid = SOLID_BSP;
+		gi.linkentity (self);
+		return;
+	}
+
+	// it must be TRIGGER_SPAWN
+	if (!(self->spawnflags & 1))
+	{
+//		gi.dprintf("func_wall missing TRIGGER_SPAWN\n");
+		self->spawnflags |= 1;
+	}
+
+	// yell if the spawnflags are odd
+	if (self->spawnflags & 4)
+	{
+		if (!(self->spawnflags & 2))
+		{
+			gi.dprintf("func_wall START_ON without TOGGLE\n");
+			self->spawnflags |= 2;
+		}
+	}
+
+	self->use = func_wall_use;
+	if (self->spawnflags & 4)
+	{
+		self->solid = SOLID_BSP;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
+This is solid bmodel that will fall if it's support it removed.
+*/
+
+void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *)
+{
+	// only squash thing we fall on top of
+	if (!plane)
+		return;
+	if (plane->normal[2] < 1.0)
+		return;
+	if (other->takedamage == DAMAGE_NO)
+		return;
+	T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void func_object_release (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->touch = func_object_touch;
+}
+
+void func_object_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	func_object_release (self);
+}
+
+void SP_func_object (edict_t *self)
+{
+	gi.setmodel (self, self->model);
+
+	self->mins[0] += 1;
+	self->mins[1] += 1;
+	self->mins[2] += 1;
+	self->maxs[0] -= 1;
+	self->maxs[1] -= 1;
+	self->maxs[2] -= 1;
+
+	if (!self->dmg)
+		self->dmg = 100;
+
+	if (self->spawnflags == 0)
+	{
+		self->solid = SOLID_BSP;
+		self->movetype = MOVETYPE_PUSH;
+		self->think = func_object_release;
+		self->nextthink = level.time + 2 * FRAMETIME;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->movetype = MOVETYPE_PUSH;
+		self->use = func_object_use;
+		self->svflags |= SVF_NOCLIENT;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	self->clipmask = MASK_MONSTERSOLID;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST
+Any brush that you want to explode or break apart.  If you want an
+ex0plosion, set dmg and it will do a radius explosion of that amount
+at the center of the bursh.
+
+If targeted it will not be shootable.
+
+health defaults to 100.
+
+mass defaults to 75.  This determines how much debris is emitted when
+it explodes.  You get one large chunk per 100 of mass (up to 8) and
+one small chunk per 25 of mass (up to 16).  So 800 gives the most.
+*/
+void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int, vec3_t)
+{
+	vec3_t	origin;
+	vec3_t	chunkorigin;
+	vec3_t	size;
+	int		count;
+	int		mass;
+
+	// bmodel origins are (0 0 0), we need to adjust that here
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	VectorCopy (origin, self->s.origin);
+
+	self->takedamage = DAMAGE_NO;
+
+	if (self->dmg)
+		T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity);
+	VectorNormalize (self->velocity);
+	VectorScale (self->velocity, 150, self->velocity);
+
+	// start chunks towards the center
+	VectorScale (size, 0.5, size);
+
+	mass = self->mass;
+	if (!mass)
+		mass = 75;
+
+	// big chunks
+	if (mass >= 100)
+	{
+		count = mass / 100;
+		if (count > 8)
+			count = 8;
+		while(count--)
+		{
+			chunkorigin[0] = origin[0] + crandom() * size[0];
+			chunkorigin[1] = origin[1] + crandom() * size[1];
+			chunkorigin[2] = origin[2] + crandom() * size[2];
+			ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin);
+		}
+	}
+
+	// small chunks
+	count = mass / 25;
+	if (count > 16)
+		count = 16;
+	while(count--)
+	{
+		chunkorigin[0] = origin[0] + crandom() * size[0];
+		chunkorigin[1] = origin[1] + crandom() * size[1];
+		chunkorigin[2] = origin[2] + crandom() * size[2];
+		ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin);
+	}
+
+	G_UseTargets (self, attacker);
+
+	if (self->dmg)
+		BecomeExplosion1 (self);
+	else
+		G_FreeEdict (self);
+}
+
+void func_explosive_use(edict_t *self, edict_t *other, edict_t *)
+{
+	func_explosive_explode (self, self, other, self->health, vec3_origin);
+}
+
+void func_explosive_spawn (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	gi.linkentity (self);
+}
+
+void SP_func_explosive (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 1)
+	{
+		self->svflags |= SVF_NOCLIENT;
+		self->solid = SOLID_NOT;
+		self->use = func_explosive_spawn;
+	}
+	else
+	{
+		self->solid = SOLID_BSP;
+		if (self->targetname)
+			self->use = func_explosive_use;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	if (self->use != func_explosive_use)
+	{
+		if (!self->health)
+			self->health = 100;
+		self->die = func_explosive_explode;
+		self->takedamage = DAMAGE_YES;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
+Large exploding box.  You can override its mass (100),
+health (80), and dmg (150).
+*/
+
+void barrel_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+
+{
+	float	ratio;
+	vec3_t	v;
+
+	if ((!other->groundentity) || (other->groundentity == self))
+		return;
+
+	ratio = (float)other->mass / (float)self->mass;
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME);
+}
+
+void barrel_explode (edict_t *self)
+{
+	vec3_t	org;
+	float	spd;
+	vec3_t	save;
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL);
+
+	VectorCopy (self->s.origin, save);
+	VectorMA (self->absmin, 0.5, self->size, self->s.origin);
+
+	// a few big chunks
+	spd = 1.5 * (float)self->dmg / 200.0;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+
+	// bottom corners
+	spd = 1.75 * (float)self->dmg / 200.0;
+	VectorCopy (self->absmin, org);
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+
+	// a bunch of little chunks
+	spd = 2 * self->dmg / 200;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+
+	VectorCopy (save, self->s.origin);
+	if (self->groundentity)
+		BecomeExplosion2 (self);
+	else
+		BecomeExplosion1 (self);
+}
+
+void barrel_delay (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	self->nextthink = level.time + 2 * FRAMETIME;
+	self->think = barrel_explode;
+	self->activator = attacker;
+}
+
+void SP_misc_explobox (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+	gi.modelindex ("models/objects/debris3/tris.md2");
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+
+	self->model = "models/objects/barrels/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 40);
+
+	if (!self->mass)
+		self->mass = 400;
+	if (!self->health)
+		self->health = 10;
+	if (!self->dmg)
+		self->dmg = 150;
+
+	self->die = barrel_delay;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.aiflags = AI_NOSTEP;
+
+	self->touch = barrel_touch;
+
+	self->think = M_droptofloor;
+	self->nextthink = level.time + 2 * FRAMETIME;
+
+	gi.linkentity (self);
+}
+
+
+//
+// miscellaneous specialty items
+//
+
+/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
+*/
+
+void misc_blackhole_use (edict_t *ent, edict_t *, edict_t *)
+{
+	/*
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	*/
+	G_FreeEdict (ent);
+}
+
+void misc_blackhole_think (edict_t *self)
+{
+	if (++self->s.frame < 19)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 0;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_blackhole (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 8);
+	ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2");
+	ent->s.renderfx = RF_TRANSLUCENT;
+	ent->use = misc_blackhole_use;
+	ent->think = misc_blackhole_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
+*/
+
+void misc_eastertank_think (edict_t *self)
+{
+	if (++self->s.frame < 293)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 254;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_eastertank (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, -16);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
+	ent->s.frame = 254;
+	ent->think = misc_eastertank_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick_think (edict_t *self)
+{
+	if (++self->s.frame < 247)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 208;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 208;
+	ent->think = misc_easterchick_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick2_think (edict_t *self)
+{
+	if (++self->s.frame < 287)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 248;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 248;
+	ent->think = misc_easterchick2_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
+Not really a monster, this is the Tank Commander's decapitated body.
+There should be a item_commander_head that has this as it's target.
+*/
+
+void commander_body_think (edict_t *self)
+{
+	if (++self->s.frame < 24)
+		self->nextthink = level.time + FRAMETIME;
+	else
+		self->nextthink = 0;
+
+	if (self->s.frame == 22)
+		gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->think = commander_body_think;
+	self->nextthink = level.time + FRAMETIME;
+	gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_drop (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->s.origin[2] += 2;
+}
+
+void SP_monster_commander_body (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_BBOX;
+	self->model = "models/monsters/commandr/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -32, -32, 0);
+	VectorSet (self->maxs, 32, 32, 48);
+	self->use = commander_body_use;
+	self->takedamage = DAMAGE_YES;
+	self->flags = FL_GODMODE;
+	self->s.renderfx |= RF_FRAMELERP;
+	gi.linkentity (self);
+
+	gi.soundindex ("tank/thud.wav");
+	gi.soundindex ("tank/pain.wav");
+
+	self->think = commander_body_drop;
+	self->nextthink = level.time + 5 * FRAMETIME;
+}
+
+
+/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
+The origin is the bottom of the banner.
+The banner is 128 tall.
+*/
+void misc_banner_think (edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 16;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_banner (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	ent->s.frame = rand() % 16;
+	gi.linkentity (ent);
+
+	ent->think = misc_banner_think;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
+This is the dead player model. Comes in 6 exciting different poses!
+*/
+void misc_deadsoldier_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health > -80)
+		return;
+
+	gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+	for (n= 0; n < 4; n++)
+		ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+	ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+}
+
+void SP_misc_deadsoldier (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2");
+
+	// Defaults to frame 0
+	if (ent->spawnflags & 2)
+		ent->s.frame = 1;
+	else if (ent->spawnflags & 4)
+		ent->s.frame = 2;
+	else if (ent->spawnflags & 8)
+		ent->s.frame = 3;
+	else if (ent->spawnflags & 16)
+		ent->s.frame = 4;
+	else if (ent->spawnflags & 32)
+		ent->s.frame = 5;
+	else
+		ent->s.frame = 0;
+
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 16);
+	ent->deadflag = DEAD_DEAD;
+	ent->takedamage = DAMAGE_YES;
+	ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER;
+	ent->die = misc_deadsoldier_die;
+	ent->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
+This is the Viper for the flyby bombing.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast the Viper should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_viper_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_viper (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_viper_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) 
+This is a large stationary viper as seen in Paul's intro
+*/
+void SP_misc_bigviper (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -176, -120, -24);
+	VectorSet (ent->maxs, 176, 120, 72);
+	ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
+"dmg"	how much boom should the bomb make?
+*/
+void misc_viper_bomb_touch (edict_t *self, edict_t *, cplane_t *, csurface_t *)
+{
+	G_UseTargets (self, self->activator);
+
+	self->s.origin[2] = self->absmin[2] + 1;
+	T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB);
+	BecomeExplosion2 (self);
+}
+
+void misc_viper_bomb_prethink (edict_t *self)
+{
+	vec3_t	v;
+	float	diff;
+
+	self->groundentity = NULL;
+
+	diff = self->timestamp - level.time;
+	if (diff < -1.0)
+		diff = -1.0;
+
+	VectorScale (self->moveinfo.dir, 1.0 + diff, v);
+	v[2] = diff;
+
+	diff = self->s.angles[2];
+	vectoangles (v, self->s.angles);
+	self->s.angles[2] = diff + 10;
+}
+
+void misc_viper_bomb_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*viper;
+
+	self->solid = SOLID_BBOX;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->s.effects |= EF_ROCKET;
+	self->use = NULL;
+	self->movetype = MOVETYPE_TOSS;
+	self->prethink = misc_viper_bomb_prethink;
+	self->touch = misc_viper_bomb_touch;
+	self->activator = activator;
+
+	viper = G_Find (NULL, FOFS(classname), "misc_viper");
+	VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
+
+	self->timestamp = level.time;
+	VectorCopy (viper->moveinfo.dir, self->moveinfo.dir);
+}
+
+void SP_misc_viper_bomb (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+
+	self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
+
+	if (!self->dmg)
+		self->dmg = 1000;
+
+	self->use = misc_viper_bomb_use;
+	self->svflags |= SVF_NOCLIENT;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
+This is a Storgg ship for the flybys.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast it should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_strogg_ship_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_strogg_ship (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_strogg_ship_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+*/
+void misc_satellite_dish_think (edict_t *self)
+{
+	self->s.frame++;
+	if (self->s.frame < 38)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void misc_satellite_dish_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->s.frame = 0;
+	self->think = misc_satellite_dish_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_satellite_dish (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 128);
+	ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2");
+	ent->use = misc_satellite_dish_use;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine1 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_arm (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/arm/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_leg (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/leg/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_head (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/head/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+//=====================================================
+
+/*QUAKED target_character (0 0 1) ?
+used with target_string (must be on same "team")
+"count" is position in the string (starts at 1)
+*/
+
+void SP_target_character (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	self->s.frame = 12;
+	gi.linkentity (self);
+	return;
+}
+
+
+/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+*/
+
+void target_string_use (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t *e;
+	int		n, l;
+	char	c;
+
+	l = strlen(self->message);
+	for (e = self->teammaster; e; e = e->teamchain)
+	{
+		if (!e->count)
+			continue;
+		n = e->count - 1;
+		if (n > l)
+		{
+			e->s.frame = 12;
+			continue;
+		}
+
+		c = self->message[n];
+		if (c >= '0' && c <= '9')
+			e->s.frame = c - '0';
+		else if (c == '-')
+			e->s.frame = 10;
+		else if (c == ':')
+			e->s.frame = 11;
+		else
+			e->s.frame = 12;
+	}
+}
+
+void SP_target_string (edict_t *self)
+{
+	if (!self->message)
+		self->message = "";
+	self->use = target_string_use;
+}
+
+
+/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
+target a target_string with this
+
+The default is to be a time of day clock
+
+TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+If START_OFF, this entity must be used before it starts
+
+"style"		0 "xx"
+			1 "xx:xx"
+			2 "xx:xx:xx"
+*/
+
+#define	CLOCK_MESSAGE_SIZE	16
+
+// don't let field width of any clock messages change, or it
+// could cause an overwrite after a game load
+
+static void func_clock_reset (edict_t *self)
+{
+	self->activator = NULL;
+	if (self->spawnflags & 1)
+	{
+		self->health = 0;
+		self->wait = self->count;
+	}
+	else if (self->spawnflags & 2)
+	{
+		self->health = self->count;
+		self->wait = 0;
+	}
+}
+
+static void func_clock_format_countdown (edict_t *self)
+{
+	if (self->style == 0)
+	{
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
+		return;
+	}
+
+	if (self->style == 1)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		return;
+	}
+
+	if (self->style == 2)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+		return;
+	}
+}
+
+void func_clock_think (edict_t *self)
+{
+	Tm *t;
+
+	if (!self->enemy)
+	{
+		self->enemy = G_Find (NULL, FOFS(targetname), self->target);
+		if (!self->enemy)
+			return;
+	}
+
+	if (self->spawnflags & 1)
+	{
+		func_clock_format_countdown (self);
+		self->health++;
+	}
+	else if (self->spawnflags & 2)
+	{
+		func_clock_format_countdown (self);
+		self->health--;
+	}
+	else
+	{
+		t = localtime(time(nil));
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2d:%2d:%2d", t->hour, t->min, t->sec);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+	}
+
+	self->enemy->message = self->message;
+	self->enemy->use (self->enemy, self, self);
+
+	if (((self->spawnflags & 1) && (self->health > self->wait)) ||
+		((self->spawnflags & 2) && (self->health < self->wait)))
+	{
+		if (self->pathtarget)
+		{
+			char *savetarget;
+			char *savemessage;
+
+			savetarget = self->target;
+			savemessage = self->message;
+			self->target = self->pathtarget;
+			self->message = NULL;
+			G_UseTargets (self, self->activator);
+			self->target = savetarget;
+			self->message = savemessage;
+		}
+
+		if (!(self->spawnflags & 8))
+			return;
+
+		func_clock_reset (self);
+
+		if (self->spawnflags & 4)
+			return;
+	}
+
+	self->nextthink = level.time + 1;
+}
+
+void func_clock_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (!(self->spawnflags & 8))
+		self->use = NULL;
+	if (self->activator)
+		return;
+	self->activator = activator;
+	self->think (self);
+}
+
+void SP_func_clock (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 2) && (!self->count))
+	{
+		gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 1) && (!self->count))
+		self->count = 60*60;;
+
+	func_clock_reset (self);
+
+	self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL);
+
+	self->think = func_clock_think;
+
+	if (self->spawnflags & 4)
+		self->use = func_clock_use;
+	else
+		self->nextthink = level.time + 1;
+}
+
+//=================================================================================
+
+void teleporter_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t		*dest;
+	int			i;
+
+	if (!other->client)
+		return;
+	dest = G_Find (NULL, FOFS(targetname), self->target);
+	if (!dest)
+	{
+		gi.dprintf ("Couldn't find destination\n");
+		return;
+	}
+
+//ZOID
+	CTFPlayerResetGrapple(other);
+//ZOID
+
+	// unlink to make sure it can't possibly interfere with KillBox
+	gi.unlinkentity (other);
+
+	VectorCopy (dest->s.origin, other->s.origin);
+	VectorCopy (dest->s.origin, other->s.old_origin);
+	other->s.origin[2] += 15; //10;
+
+	// clear the velocity and hold them in place briefly
+	VectorClear (other->velocity);
+	other->client->ps.pmove.pm_time = 160>>3;		// hold time
+	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+	// draw the teleport splash at source and on the player
+	self->owner->s.event = EV_PLAYER_TELEPORT;
+	other->s.event = EV_PLAYER_TELEPORT;
+
+	// set angles
+	for (i=0 ; i<3 ; i++)
+		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+
+	VectorClear (other->s.angles);
+	VectorClear (other->client->ps.viewangles);
+	VectorClear (other->client->v_angle);
+
+	// kill anything at the destination
+	KillBox (other);
+
+	gi.linkentity (other);
+}
+
+/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
+Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
+*/
+void SP_misc_teleporter (edict_t *ent)
+{
+	edict_t		*trig;
+
+	if (!ent->target)
+	{
+		gi.dprintf ("teleporter without a target.\n");
+		G_FreeEdict (ent);
+		return;
+	}
+
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 1;
+	ent->s.effects = EF_TELEPORTER;
+	ent->s.sound = gi.soundindex ("world/amb10.wav");
+	ent->solid = SOLID_BBOX;
+
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+
+	trig = G_Spawn ();
+	trig->touch = teleporter_touch;
+	trig->solid = SOLID_TRIGGER;
+	trig->target = ent->target;
+	trig->owner = ent;
+	VectorCopy (ent->s.origin, trig->s.origin);
+	VectorSet (trig->mins, -8, -8, 8);
+	VectorSet (trig->maxs, 8, 8, 24);
+	gi.linkentity (trig);
+	
+}
+
+/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+Point teleporters at these.
+*/
+void SP_misc_teleporter_dest (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 0;
+	ent->solid = SOLID_BBOX;
+//	ent->s.effects |= EF_FLIES;
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+}
+
--- /dev/null
+++ b/crbot/g_monster.c
@@ -1,0 +1,725 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+//
+// monster weapons
+//
+
+//FIXME mosnters should call these with a totally accurate direction
+// and we can mess it up based on skill.  Spread should be for normal
+// and we can tighten or loosen based on skill.  We could muck with
+// the damages too, but I'm not sure that's such a good idea.
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
+{
+	fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
+{
+	fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
+{
+	fire_blaster (self, start, dir, damage, speed, effect, false);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
+{
+	fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
+{
+	fire_rocket (self, start, dir, damage, speed, damage+20, damage);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
+{
+	fire_rail (self, start, aimdir, damage, kick);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int, float damage_radius, int flashtype)
+{
+	fire_bfg (self, start, aimdir, damage, speed, damage_radius);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+
+
+//
+// Monster utility functions
+//
+
+static void M_FliesOff (edict_t *self)
+{
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+}
+
+static void M_FliesOn (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+	self->s.effects |= EF_FLIES;
+	self->s.sound = gi.soundindex ("infantry/inflies1.wav");
+	self->think = M_FliesOff;
+	self->nextthink = level.time + 60;
+}
+
+void M_FlyCheck (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+
+	if (qrandom() > 0.5)
+		return;
+
+	self->think = M_FliesOn;
+	self->nextthink = level.time + 5 + 10 * qrandom();
+}
+
+void AttackFinished (edict_t *self, float time)
+{
+	self->monsterinfo.attack_finished = level.time + time;
+}
+
+
+void M_CheckGround (edict_t *ent)
+{
+	vec3_t		point;
+	trace_t		trace;
+
+	if (ent->flags & (FL_SWIM|FL_FLY))
+		return;
+
+	if (ent->velocity[2] > 100)
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+
+// if the hull point one-quarter unit down is solid the entity is on ground
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+	point[2] = ent->s.origin[2] - 0.25;
+
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
+
+	// check steepness
+	if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+
+//	ent->groundentity = trace.ent;
+//	ent->groundentity_linkcount = trace.ent->linkcount;
+//	if (!trace.startsolid && !trace.allsolid)
+//		VectorCopy (trace.endpos, ent->s.origin);
+	if (!trace.startsolid && !trace.allsolid)
+	{
+		VectorCopy (trace.endpos, ent->s.origin);
+		ent->groundentity = trace.ent;
+		ent->groundentity_linkcount = trace.ent->linkcount;
+		ent->velocity[2] = 0;
+	}
+}
+
+
+void M_CatagorizePosition (edict_t *ent)
+{
+	vec3_t		point;
+	int			cont;
+
+//
+// get waterlevel
+//
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+	point[2] = ent->s.origin[2] + ent->mins[2] + 1;	
+	cont = gi.pointcontents (point);
+
+	if (!(cont & MASK_WATER))
+	{
+		ent->waterlevel = 0;
+		ent->watertype = 0;
+		return;
+	}
+
+	ent->watertype = cont;
+	ent->waterlevel = 1;
+	point[2] += 26;
+	cont = gi.pointcontents (point);
+	if (!(cont & MASK_WATER))
+		return;
+
+	ent->waterlevel = 2;
+	point[2] += 22;
+	cont = gi.pointcontents (point);
+	if (cont & MASK_WATER)
+		ent->waterlevel = 3;
+}
+
+
+void M_WorldEffects (edict_t *ent)
+{
+	int		dmg;
+
+	if (ent->health > 0)
+	{
+		if (!(ent->flags & FL_SWIM))
+		{
+			if (ent->waterlevel < 3)
+			{
+				ent->air_finished = level.time + 12;
+			}
+			else if (ent->air_finished < level.time)
+			{	// drown!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+		else
+		{
+			if (ent->waterlevel > 0)
+			{
+				ent->air_finished = level.time + 9;
+			}
+			else if (ent->air_finished < level.time)
+			{	// suffocate!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+	}
+	
+	if (ent->waterlevel == 0)
+	{
+		if (ent->flags & FL_INWATER)
+		{	
+			gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+			ent->flags &= ~FL_INWATER;
+		}
+		return;
+	}
+
+	if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 0.2;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
+		}
+	}
+	if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 1;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
+		}
+	}
+	
+	if ( !(ent->flags & FL_INWATER) )
+	{	
+		if (!(ent->svflags & SVF_DEADMONSTER))
+		{
+			if (ent->watertype & CONTENTS_LAVA)
+				if (qrandom() <= 0.5)
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_SLIME)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_WATER)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		}
+
+		ent->flags |= FL_INWATER;
+		ent->damage_debounce_time = 0;
+	}
+}
+
+
+void M_droptofloor (edict_t *ent)
+{
+	vec3_t		end;
+	trace_t		trace;
+
+	ent->s.origin[2] += 1;
+	VectorCopy (ent->s.origin, end);
+	end[2] -= 256;
+	
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1 || trace.allsolid)
+		return;
+
+	VectorCopy (trace.endpos, ent->s.origin);
+
+	gi.linkentity (ent);
+	M_CheckGround (ent);
+	M_CatagorizePosition (ent);
+}
+
+
+void M_SetEffects (edict_t *ent)
+{
+	ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
+	ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
+
+	if (ent->monsterinfo.aiflags & AI_RESURRECTING)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= RF_SHELL_RED;
+	}
+
+	if (ent->health <= 0)
+		return;
+
+	if (ent->powerarmor_time > level.time)
+	{
+		if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+}
+
+
+void M_MoveFrame (edict_t *self)
+{
+	mmove_t	*move;
+	int		index;
+
+	move = self->monsterinfo.currentmove;
+	self->nextthink = level.time + FRAMETIME;
+
+	if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
+	{
+		self->s.frame = self->monsterinfo.nextframe;
+		self->monsterinfo.nextframe = 0;
+	}
+	else
+	{
+		if (self->s.frame == move->lastframe)
+		{
+			if (move->endfunc)
+			{
+				move->endfunc (self);
+
+				// regrab move, endfunc is very likely to change it
+				move = self->monsterinfo.currentmove;
+
+				// check for death
+				if (self->svflags & SVF_DEADMONSTER)
+					return;
+			}
+		}
+
+		if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
+		{
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+			self->s.frame = move->firstframe;
+		}
+		else
+		{
+			if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			{
+				self->s.frame++;
+				if (self->s.frame > move->lastframe)
+					self->s.frame = move->firstframe;
+			}
+		}
+	}
+
+	index = self->s.frame - move->firstframe;
+	if (move->frame[index].aifunc) {
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
+		else
+			move->frame[index].aifunc (self, 0);
+	}
+
+	if (move->frame[index].thinkfunc)
+		move->frame[index].thinkfunc (self);
+}
+
+
+void monster_think (edict_t *self)
+{
+	M_MoveFrame (self);
+	if (self->linkcount != self->monsterinfo.linkcount)
+	{
+		self->monsterinfo.linkcount = self->linkcount;
+		M_CheckGround (self);
+	}
+	M_CatagorizePosition (self);
+	M_WorldEffects (self);
+	M_SetEffects (self);
+}
+
+
+/*
+================
+monster_use
+
+Using a monster makes it angry at the current activator
+================
+*/
+void monster_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->enemy)
+		return;
+	if (self->health <= 0)
+		return;
+	if (activator->flags & FL_NOTARGET)
+		return;
+	if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
+		return;
+	
+// delay reaction so if the monster is teleported, its sound is still heard
+	self->enemy = activator;
+	FoundTarget (self);
+}
+
+
+void monster_start_go (edict_t *self);
+
+
+void monster_triggered_spawn (edict_t *self)
+{
+	self->s.origin[2] += 1;
+	KillBox (self);
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->air_finished = level.time + 12;
+	gi.linkentity (self);
+
+	monster_start_go (self);
+
+	if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
+	{
+		FoundTarget (self);
+	}
+	else
+	{
+		self->enemy = NULL;
+	}
+}
+
+void monster_triggered_spawn_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	// we have a one frame delay here so we don't telefrag the guy who activated us
+	self->think = monster_triggered_spawn;
+	self->nextthink = level.time + FRAMETIME;
+	if (activator->client)
+		self->enemy = activator;
+	self->use = monster_use;
+}
+
+void monster_triggered_start (edict_t *self)
+{
+	self->solid = SOLID_NOT;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+	self->use = monster_triggered_spawn_use;
+}
+
+
+/*
+================
+monster_death_use
+
+When a monster dies, it fires all of its targets with the current
+enemy as activator.
+================
+*/
+void monster_death_use (edict_t *self)
+{
+	self->flags &= ~(FL_FLY|FL_SWIM);
+	self->monsterinfo.aiflags &= AI_GOOD_GUY;
+
+	if (self->item)
+	{
+		Drop_Item (self, self->item);
+		self->item = NULL;
+	}
+
+	if (self->deathtarget)
+		self->target = self->deathtarget;
+
+	if (!self->target)
+		return;
+
+	G_UseTargets (self, self->enemy);
+}
+
+
+//============================================================================
+
+qboolean monster_start (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return false;
+	}
+
+	if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
+	{
+		self->spawnflags &= ~4;
+		self->spawnflags |= 1;
+//		gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
+	}
+
+	if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
+		level.total_monsters++;
+
+	self->nextthink = level.time + FRAMETIME;
+	self->svflags |= SVF_MONSTER;
+	self->s.renderfx |= RF_FRAMELERP;
+	self->takedamage = DAMAGE_AIM;
+	self->air_finished = level.time + 12;
+	self->use = monster_use;
+	self->max_health = self->health;
+	self->clipmask = MASK_MONSTERSOLID;
+
+	self->s.skinnum = 0;
+	self->deadflag = DEAD_NO;
+	self->svflags &= ~SVF_DEADMONSTER;
+
+	if (!self->monsterinfo.checkattack)
+		self->monsterinfo.checkattack = M_CheckAttack;
+	VectorCopy (self->s.origin, self->s.old_origin);
+
+	if (st.item)
+	{
+		self->item = FindItemByClassname (st.item);
+		if (!self->item)
+			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+	}
+
+	// randomize what frame they start on
+	if (self->monsterinfo.currentmove)
+		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
+
+	return true;
+}
+
+void monster_start_go (edict_t *self)
+{
+	vec3_t	v;
+
+	if (self->health <= 0)
+		return;
+
+	// check for target to combat_point and change to combattarget
+	if (self->target)
+	{
+		qboolean	notcombat;
+		qboolean	fixup;
+		edict_t		*target;
+
+		target = NULL;
+		notcombat = false;
+		fixup = false;
+		while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") == 0)
+			{
+				self->combattarget = self->target;
+				fixup = true;
+			}
+			else
+			{
+				notcombat = true;
+			}
+		}
+		if (notcombat && self->combattarget)
+			gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
+		if (fixup)
+			self->target = NULL;
+	}
+
+	// validate combattarget
+	if (self->combattarget)
+	{
+		edict_t		*target;
+
+		target = NULL;
+		while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") != 0)
+			{
+				gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
+					self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
+					self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
+					(int)target->s.origin[2]);
+			}
+		}
+	}
+
+	if (self->target)
+	{
+		self->goalentity = self->movetarget = G_PickTarget(self->target);
+		if (!self->movetarget)
+		{
+			gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
+			self->target = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+		else if (strcmp (self->movetarget->classname, "path_corner") == 0)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+			self->monsterinfo.walk (self);
+			self->target = NULL;
+		}
+		else
+		{
+			self->goalentity = self->movetarget = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+	}
+	else
+	{
+		self->monsterinfo.pausetime = 100000000;
+		self->monsterinfo.stand (self);
+	}
+
+	self->think = monster_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void walkmonster_start_go (edict_t *self)
+{
+	if (!(self->spawnflags & 2) && level.time < 1)
+	{
+		M_droptofloor (self);
+
+		if (self->groundentity)
+			if (!M_walkmove (self, 0, 0))
+				gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+	}
+	
+	if (!self->yaw_speed)
+		self->yaw_speed = 20;
+	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void walkmonster_start (edict_t *self)
+{
+	self->think = walkmonster_start_go;
+	monster_start (self);
+}
+
+
+void flymonster_start_go (edict_t *self)
+{
+	if (!M_walkmove (self, 0, 0))
+		gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+
+void flymonster_start (edict_t *self)
+{
+	self->flags |= FL_FLY;
+	self->think = flymonster_start_go;
+	monster_start (self);
+}
+
+
+void swimmonster_start_go (edict_t *self)
+{
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 10;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void swimmonster_start (edict_t *self)
+{
+	self->flags |= FL_SWIM;
+	self->think = swimmonster_start_go;
+	monster_start (self);
+}
--- /dev/null
+++ b/crbot/g_phys.c
@@ -1,0 +1,946 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+
+
+pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
+
+onground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects 
+
+doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
+bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
+corpses are SOLID_NOT and MOVETYPE_TOSS
+crates are SOLID_BBOX and MOVETYPE_TOSS
+walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
+flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
+
+solid_edge items only clip against bsp models.
+
+*/
+
+
+/*
+============
+SV_TestEntityPosition
+
+============
+*/
+edict_t	*SV_TestEntityPosition (edict_t *ent)
+{
+	trace_t	trace;
+	int		mask;
+
+	
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
+
+	if (trace.startsolid)
+		return g_edicts;
+		
+	return NULL;
+}
+
+
+/*
+================
+SV_CheckVelocity
+================
+*/
+void SV_CheckVelocity (edict_t *ent)
+{
+	int		i;
+
+//
+// bound velocity
+//
+	for (i=0 ; i<3 ; i++)
+	{
+		if (ent->velocity[i] > sv_maxvelocity->value)
+			ent->velocity[i] = sv_maxvelocity->value;
+		else if (ent->velocity[i] < -sv_maxvelocity->value)
+			ent->velocity[i] = -sv_maxvelocity->value;
+	}
+}
+
+/*
+=============
+SV_RunThink
+
+Runs thinking code for this frame if necessary
+=============
+*/
+qboolean SV_RunThink (edict_t *ent)
+{
+	float	thinktime;
+
+	thinktime = ent->nextthink;
+	if (thinktime <= 0)
+		return true;
+	if (thinktime > level.time+0.001)
+		return true;
+	
+	ent->nextthink = 0;
+	if (!ent->think)
+		gi.error ("NULL ent->think");
+	ent->think (ent);
+
+	return false;
+}
+
+/*
+==================
+SV_Impact
+
+Two entities have touched, so run their touch functions
+==================
+*/
+void SV_Impact (edict_t *e1, trace_t *trace)
+{
+	edict_t		*e2;
+//	cplane_t	backplane;
+
+	e2 = trace->ent;
+
+	if (e1->touch && e1->solid != SOLID_NOT)
+		e1->touch (e1, e2, &trace->plane, trace->surface);
+	
+	if (e2->touch && e2->solid != SOLID_NOT)
+		e2->touch (e2, e1, NULL, NULL);
+}
+
+
+/*
+==================
+ClipVelocity
+
+Slide off of the impacting object
+returns the blocked flags (1 = floor, 2 = step / wall)
+==================
+*/
+#define	STOP_EPSILON	0.1
+
+int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
+{
+	float	backoff;
+	float	change;
+	int		i, blocked;
+	
+	blocked = 0;
+	if (normal[2] > 0)
+		blocked |= 1;		// floor
+	if (!normal[2])
+		blocked |= 2;		// step
+	
+	backoff = DotProduct (in, normal) * overbounce;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		change = normal[i]*backoff;
+		out[i] = in[i] - change;
+		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
+			out[i] = 0;
+	}
+	
+	return blocked;
+}
+
+
+/*
+============
+SV_FlyMove
+
+The basic solid body movement clip that slides along multiple planes
+Returns the clipflags if the velocity was modified (hit something solid)
+1 = floor
+2 = wall / step
+4 = dead stop
+============
+*/
+#define	MAX_CLIP_PLANES	5
+int SV_FlyMove (edict_t *ent, float time, int mask)
+{
+	edict_t		*hit;
+	int			bumpcount, numbumps;
+	vec3_t		dir;
+	float		d;
+	int			numplanes;
+	vec3_t		planes[MAX_CLIP_PLANES];
+	vec3_t		primal_velocity, original_velocity, new_velocity;
+	int			i, j;
+	trace_t		trace;
+	vec3_t		end;
+	float		time_left;
+	int			blocked;
+	
+	numbumps = 4;
+	
+	blocked = 0;
+	VectorCopy (ent->velocity, original_velocity);
+	VectorCopy (ent->velocity, primal_velocity);
+	numplanes = 0;
+	
+	time_left = time;
+
+	ent->groundentity = NULL;
+	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
+	{
+		for (i=0 ; i<3 ; i++)
+			end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
+
+		trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
+
+		if (trace.allsolid)
+		{	// entity is trapped in another solid
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		if (trace.fraction > 0)
+		{	// actually covered some distance
+			VectorCopy (trace.endpos, ent->s.origin);
+			VectorCopy (ent->velocity, original_velocity);
+			numplanes = 0;
+		}
+
+		if (trace.fraction == 1)
+			 break;		// moved the entire distance
+
+		hit = trace.ent;
+
+		if (trace.plane.normal[2] > 0.7)
+		{
+			blocked |= 1;		// floor
+			if ( hit->solid == SOLID_BSP)
+			{
+				ent->groundentity = hit;
+				ent->groundentity_linkcount = hit->linkcount;
+			}
+		}
+		if (!trace.plane.normal[2])
+		{
+			blocked |= 2;		// step
+		}
+
+//
+// run the impact function
+//
+		SV_Impact (ent, &trace);
+		if (!ent->inuse)
+			break;		// removed by the impact function
+
+		
+		time_left -= time_left * trace.fraction;
+		
+	// cliped to another plane
+		if (numplanes >= MAX_CLIP_PLANES)
+		{	// this shouldn't really happen
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		VectorCopy (trace.plane.normal, planes[numplanes]);
+		numplanes++;
+
+//
+// modify original_velocity so it parallels all of the clip planes
+//
+		for (i=0 ; i<numplanes ; i++)
+		{
+			ClipVelocity (original_velocity, planes[i], new_velocity, 1);
+			for (j=0 ; j<numplanes ; j++)
+				if (j != i)
+				{
+					if (DotProduct (new_velocity, planes[j]) < 0)
+						break;	// not ok
+				}
+			if (j == numplanes)
+				break;
+		}
+		
+		if (i != numplanes)
+		{	// go along this plane
+			VectorCopy (new_velocity, ent->velocity);
+		}
+		else
+		{	// go along the crease
+			if (numplanes != 2)
+			{
+//				gi.dprintf ("clip velocity, numplanes == %i\n",numplanes);
+				VectorCopy (vec3_origin, ent->velocity);
+				return 7;
+			}
+			CrossProduct (planes[0], planes[1], dir);
+			d = DotProduct (dir, ent->velocity);
+			VectorScale (dir, d, ent->velocity);
+		}
+
+//
+// if original velocity is against the original velocity, stop dead
+// to avoid tiny occilations in sloping corners
+//
+		if (DotProduct (ent->velocity, primal_velocity) <= 0)
+		{
+			VectorCopy (vec3_origin, ent->velocity);
+			return blocked;
+		}
+	}
+
+	return blocked;
+}
+
+
+/*
+============
+SV_AddGravity
+
+============
+*/
+void SV_AddGravity (edict_t *ent)
+{
+    if ( ent->bot_info && ent->bot_info->b_on_ladder ) return;
+
+	ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+}
+
+/*
+===============================================================================
+
+PUSHMOVE
+
+===============================================================================
+*/
+
+/*
+============
+SV_PushEntity
+
+Does not change the entities velocity at all
+============
+*/
+trace_t SV_PushEntity (edict_t *ent, vec3_t push)
+{
+	trace_t	trace;
+	vec3_t	start;
+	vec3_t	end;
+	int		mask;
+
+	VectorCopy (ent->s.origin, start);
+	VectorAdd (start, push, end);
+
+retry:
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+
+	trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask);
+	
+	VectorCopy (trace.endpos, ent->s.origin);
+	gi.linkentity (ent);
+
+	if (trace.fraction != 1.0)
+	{
+		SV_Impact (ent, &trace);
+
+		// if the pushed entity went away and the pusher is still there
+		if (!trace.ent->inuse && ent->inuse)
+		{
+			// move the pusher back and try again
+			VectorCopy (start, ent->s.origin);
+			gi.linkentity (ent);
+			goto retry;
+		}
+	}
+
+	if (ent->inuse)
+		G_TouchTriggers (ent);
+
+	return trace;
+}					
+
+
+typedef struct
+{
+	edict_t	*ent;
+	vec3_t	origin;
+	vec3_t	angles;
+	float	deltayaw;
+} pushed_t;
+pushed_t	pushed[MAX_EDICTS], *pushed_p;
+
+edict_t	*obstacle;
+
+/*
+============
+SV_Push
+
+Objects need to be moved back on a failed push,
+otherwise riders would continue to slide.
+============
+*/
+qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
+{
+	int			i, e;
+	edict_t		*check, *block;
+	vec3_t		mins, maxs;
+	pushed_t	*p;
+	vec3_t		org, org2, move2, forward, right, up;
+
+	// clamp the move to 1/8 units, so the position will
+	// be accurate for client side prediction
+	for (i=0 ; i<3 ; i++)
+	{
+		float	temp;
+		temp = move[i]*8.0;
+		if (temp > 0.0)
+			temp += 0.5;
+		else
+			temp -= 0.5;
+		move[i] = 0.125 * (int)temp;
+	}
+
+	// find the bounding box
+	for (i=0 ; i<3 ; i++)
+	{
+		mins[i] = pusher->absmin[i] + move[i];
+		maxs[i] = pusher->absmax[i] + move[i];
+	}
+
+// we need this for pushing things later
+	VectorSubtract (vec3_origin, amove, org);
+	AngleVectors (org, forward, right, up);
+
+// save the pusher's original position
+	pushed_p->ent = pusher;
+	VectorCopy (pusher->s.origin, pushed_p->origin);
+	VectorCopy (pusher->s.angles, pushed_p->angles);
+	if (pusher->client)
+		pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
+	pushed_p++;
+
+// move the pusher to it's final position
+	VectorAdd (pusher->s.origin, move, pusher->s.origin);
+	VectorAdd (pusher->s.angles, amove, pusher->s.angles);
+	gi.linkentity (pusher);
+
+// see if any solid entities are inside the final position
+	check = g_edicts+1;
+	for (e = 1; e < globals.num_edicts; e++, check++)
+	{
+		if (!check->inuse)
+			continue;
+		if (check->movetype == MOVETYPE_PUSH
+		|| check->movetype == MOVETYPE_STOP
+		|| check->movetype == MOVETYPE_NONE
+		|| check->movetype == MOVETYPE_NOCLIP)
+			continue;
+
+		if (!check->area.prev)
+			continue;		// not linked in anywhere
+
+	// if the entity is standing on the pusher, it will definitely be moved
+		if (check->groundentity != pusher)
+		{
+			// see if the ent needs to be tested
+			if ( check->absmin[0] >= maxs[0]
+			|| check->absmin[1] >= maxs[1]
+			|| check->absmin[2] >= maxs[2]
+			|| check->absmax[0] <= mins[0]
+			|| check->absmax[1] <= mins[1]
+			|| check->absmax[2] <= mins[2] )
+				continue;
+
+			// see if the ent's bbox is inside the pusher's final position
+			if (!SV_TestEntityPosition (check))
+				continue;
+		}
+
+		if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
+		{
+			// move this entity
+			pushed_p->ent = check;
+			VectorCopy (check->s.origin, pushed_p->origin);
+			VectorCopy (check->s.angles, pushed_p->angles);
+			pushed_p++;
+
+			// try moving the contacted entity 
+			VectorAdd (check->s.origin, move, check->s.origin);
+			if (check->client)
+			{	// FIXME: doesn't rotate monsters?
+				check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
+			}
+
+			// figure movement due to the pusher's amove
+			VectorSubtract (check->s.origin, pusher->s.origin, org);
+			org2[0] = DotProduct (org, forward);
+			org2[1] = -DotProduct (org, right);
+			org2[2] = DotProduct (org, up);
+			VectorSubtract (org2, org, move2);
+			VectorAdd (check->s.origin, move2, check->s.origin);
+
+			// may have pushed them off an edge
+			if (check->groundentity != pusher)
+				check->groundentity = NULL;
+
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{	// pushed ok
+				gi.linkentity (check);
+				// impact?
+				continue;
+			}
+
+			// if it is ok to leave in the old position, do it
+			// this is only relevent for riding entities, not pushed
+			// FIXME: this doesn't acount for rotation
+			VectorSubtract (check->s.origin, move, check->s.origin);
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{
+				pushed_p--;
+				continue;
+			}
+		}
+		
+		// save off the obstacle so we can call the block function
+		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.origin);
+			VectorCopy (p->angles, p->ent->s.angles);
+			if (p->ent->client)
+			{
+				p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
+			}
+			gi.linkentity (p->ent);
+		}
+		return false;
+	}
+
+//FIXME: is there a better way to handle this?
+	// see if anything we moved has touched a trigger
+	for (p=pushed_p-1 ; p>=pushed ; p--)
+		G_TouchTriggers (p->ent);
+
+	return true;
+}
+
+/*
+================
+SV_Physics_Pusher
+
+Bmodel objects don't interact with each other, but
+push all box objects
+================
+*/
+void SV_Physics_Pusher (edict_t *ent)
+{
+	vec3_t		move, amove;
+	edict_t		*part, *mv;
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	// 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
+//retry:
+	pushed_p = pushed;
+	for (part = ent ; part ; part=part->teamchain)
+	{
+		if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
+			part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
+			)
+		{	// object is moving
+			VectorScale (part->velocity, FRAMETIME, move);
+			VectorScale (part->avelocity, FRAMETIME, amove);
+
+			if (!SV_Push (part, move, amove))
+				break;	// move was blocked
+		}
+	}
+	if (pushed_p > &pushed[MAX_EDICTS])
+		gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
+
+	if (part)
+	{
+		// the move failed, bump all nextthink times and back out moves
+		for (mv = ent ; mv ; mv=mv->teamchain)
+		{
+			if (mv->nextthink > 0)
+				mv->nextthink += FRAMETIME;
+		}
+
+		// if the pusher has a "blocked" function, call it
+		// otherwise, just stay in place until the obstacle is gone
+		if (part->blocked)
+			part->blocked (part, obstacle);
+/*
+		// if the pushed entity went away and the pusher is still there
+		if (!obstacle->inuse && part->inuse)
+			goto retry;
+*/
+	}
+	else
+	{
+		// the move succeeded, so call all think functions
+		for (part = ent ; part ; part=part->teamchain)
+		{
+			SV_RunThink (part);
+		}
+	}
+}
+
+//==================================================================
+
+/*
+=============
+SV_Physics_None
+
+Non moving objects can only think
+=============
+*/
+void SV_Physics_None (edict_t *ent)
+{
+// regular thinking
+	SV_RunThink (ent);
+}
+
+/*
+=============
+SV_Physics_Noclip
+
+A moving object that doesn't obey physics
+=============
+*/
+void SV_Physics_Noclip (edict_t *ent)
+{
+// regular thinking
+	if (!SV_RunThink (ent))
+		return;
+	
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
+
+	gi.linkentity (ent);
+}
+
+/*
+==============================================================================
+
+TOSS / BOUNCE
+
+==============================================================================
+*/
+
+/*
+=============
+SV_Physics_Toss
+
+Toss, bounce, and fly movement.  When onground, do nothing.
+=============
+*/
+void SV_Physics_Toss (edict_t *ent)
+{
+	trace_t		trace;
+	vec3_t		move;
+	float		backoff;
+	edict_t		*slave;
+	qboolean	wasinwater;
+	qboolean	isinwater;
+	vec3_t		old_origin;
+
+// regular thinking
+	SV_RunThink (ent);
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	if (ent->velocity[2] > 0)
+		ent->groundentity = NULL;
+
+// check for the groundentity going away
+	if (ent->groundentity)
+		if (!ent->groundentity->inuse)
+			ent->groundentity = NULL;
+
+// if onground, return without moving
+	if ( ent->groundentity )
+		return;
+
+	VectorCopy (ent->s.origin, old_origin);
+
+	SV_CheckVelocity (ent);
+
+// add gravity
+	if (ent->movetype != MOVETYPE_FLY
+	&& ent->movetype != MOVETYPE_FLYMISSILE)
+		SV_AddGravity (ent);
+
+// move angles
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+
+// move origin
+	VectorScale (ent->velocity, FRAMETIME, move);
+	trace = SV_PushEntity (ent, move);
+	if (!ent->inuse)
+		return;
+
+	if (trace.fraction < 1)
+	{
+		if (ent->movetype == MOVETYPE_BOUNCE)
+			backoff = 1.5;
+		else
+			backoff = 1;
+
+		ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff);
+
+	// stop if on ground
+		if (trace.plane.normal[2] > 0.7)
+		{		
+			if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE )
+			{
+				ent->groundentity = trace.ent;
+				ent->groundentity_linkcount = trace.ent->linkcount;
+				VectorCopy (vec3_origin, ent->velocity);
+				VectorCopy (vec3_origin, ent->avelocity);
+			}
+		}
+
+//		if (ent->touch)
+//			ent->touch (ent, trace.ent, &trace.plane, trace.surface);
+	}
+	
+// check for water transition
+	wasinwater = (ent->watertype & MASK_WATER);
+	ent->watertype = gi.pointcontents (ent->s.origin);
+	isinwater = ent->watertype & MASK_WATER;
+
+	if (isinwater)
+		ent->waterlevel = 1;
+	else
+		ent->waterlevel = 0;
+
+	if (!wasinwater && isinwater)
+		gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+	else if (wasinwater && !isinwater)
+		gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+
+// move teamslaves
+	for (slave = ent->teamchain; slave; slave = slave->teamchain)
+	{
+		VectorCopy (ent->s.origin, slave->s.origin);
+		gi.linkentity (slave);
+	}
+}
+
+/*
+===============================================================================
+
+STEPPING MOVEMENT
+
+===============================================================================
+*/
+
+/*
+=============
+SV_Physics_Step
+
+Monsters freefall when they don't have a ground entity, otherwise
+all movement is done with discrete steps.
+
+This is also used for objects that have become still on the ground, but
+will fall if the floor is pulled out from under them.
+FIXME: is this true?
+=============
+*/
+
+//FIXME: hacked in for E3 demo
+#define	sv_stopspeed		100
+#define sv_friction			6
+#define sv_waterfriction	1
+
+void SV_AddRotationalFriction (edict_t *ent)
+{
+	int		n;
+	float	adjustment;
+
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	adjustment = FRAMETIME * sv_stopspeed * sv_friction;
+	for (n = 0; n < 3; n++)
+	{
+		if (ent->avelocity[n] > 0)
+		{
+			ent->avelocity[n] -= adjustment;
+			if (ent->avelocity[n] < 0)
+				ent->avelocity[n] = 0;
+		}
+		else
+		{
+			ent->avelocity[n] += adjustment;
+			if (ent->avelocity[n] > 0)
+				ent->avelocity[n] = 0;
+		}
+	}
+}
+
+void SV_Physics_Step (edict_t *ent)
+{
+	qboolean	wasonground;
+	qboolean	hitsound = false;
+	float		*vel;
+	float		speed, newspeed, control;
+	float		friction;
+	edict_t		*groundentity;
+	int			mask;
+
+	// airborn monsters should always check for ground
+	if (!ent->groundentity)
+		M_CheckGround (ent);
+
+	groundentity = ent->groundentity;
+
+	SV_CheckVelocity (ent);
+
+	if (groundentity)
+		wasonground = true;
+	else
+		wasonground = false;
+		
+	if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+		SV_AddRotationalFriction (ent);
+
+	// add gravity except:
+	//   flying monsters
+	//   swimming monsters who are in the water
+	if (! wasonground)
+		if (!(ent->flags & FL_FLY))
+			if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
+			{
+				if (ent->velocity[2] < sv_gravity->value*-0.1)
+					hitsound = true;
+				if (ent->waterlevel == 0)
+					SV_AddGravity (ent);
+			}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed ? sv_stopspeed : speed;
+		friction = sv_friction/3;
+		newspeed = speed - (FRAMETIME * control * friction);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed ? sv_stopspeed : speed;
+		newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
+	{
+		// apply friction
+		// let dead monsters who aren't completely onground slide
+		if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY)))
+			if (!(ent->health <= 0.0 && !M_CheckBottom(ent)))
+			{
+				vel = ent->velocity;
+				speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
+				if (speed)
+				{
+					friction = sv_friction;
+
+					control = speed < sv_stopspeed ? sv_stopspeed : speed;
+					newspeed = speed - FRAMETIME*control*friction;
+
+					if (newspeed < 0)
+						newspeed = 0;
+					newspeed /= speed;
+
+					vel[0] *= newspeed;
+					vel[1] *= newspeed;
+				}
+			}
+
+		if (ent->svflags & SVF_MONSTER)
+			mask = MASK_MONSTERSOLID;
+		else
+			mask = MASK_SOLID;
+		SV_FlyMove (ent, FRAMETIME, mask);
+
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+
+		if (ent->groundentity)
+			if (!wasonground)
+				if (hitsound)
+					gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
+	}
+
+// regular thinking
+	SV_RunThink (ent);
+}
+
+//============================================================================
+/*
+================
+G_RunEntity
+
+================
+*/
+void G_RunEntity (edict_t *ent)
+{
+	if (ent->prethink)
+		ent->prethink (ent);
+
+	switch ( (int)ent->movetype)
+	{
+	case MOVETYPE_PUSH:
+	case MOVETYPE_STOP:
+		SV_Physics_Pusher (ent);
+		break;
+	case MOVETYPE_NONE:
+		SV_Physics_None (ent);
+		break;
+	case MOVETYPE_NOCLIP:
+		SV_Physics_Noclip (ent);
+		break;
+	case MOVETYPE_STEP:
+		SV_Physics_Step (ent);
+		break;
+	case MOVETYPE_TOSS:
+	case MOVETYPE_BOUNCE:
+	case MOVETYPE_FLY:
+	case MOVETYPE_FLYMISSILE:
+		SV_Physics_Toss (ent);
+		break;
+	default:
+		gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype);			
+	}
+
+    if (ent->bot_info && ent->bot_info->postthink) ent->bot_info->postthink(ent);
+}
--- /dev/null
+++ b/crbot/g_save.c
@@ -1,0 +1,800 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define FFL_NOSPAWN 2
+
+field_t fields[] = {
+	{"classname", FOFS(classname), F_LSTRING},
+	{"model", FOFS(model), F_LSTRING},
+	{"spawnflags", FOFS(spawnflags), F_INT},
+	{"speed", FOFS(speed), F_FLOAT},
+	{"accel", FOFS(accel), F_FLOAT},
+	{"decel", FOFS(decel), F_FLOAT},
+	{"target", FOFS(target), F_LSTRING},
+	{"targetname", FOFS(targetname), F_LSTRING},
+	{"pathtarget", FOFS(pathtarget), F_LSTRING},
+	{"deathtarget", FOFS(deathtarget), F_LSTRING},
+	{"killtarget", FOFS(killtarget), F_LSTRING},
+	{"combattarget", FOFS(combattarget), F_LSTRING},
+	{"message", FOFS(message), F_LSTRING},
+	{"team", FOFS(team), F_LSTRING},
+	{"wait", FOFS(wait), F_FLOAT},
+	{"delay", FOFS(delay), F_FLOAT},
+	{"random", FOFS(random), F_FLOAT},
+	{"move_origin", FOFS(move_origin), F_VECTOR},
+	{"move_angles", FOFS(move_angles), F_VECTOR},
+	{"style", FOFS(style), F_INT},
+	{"count", FOFS(count), F_INT},
+	{"health", FOFS(health), F_INT},
+	{"sounds", FOFS(sounds), F_INT},
+	{"light", 0, F_IGNORE},
+	{"dmg", FOFS(dmg), F_INT},
+	{"mass", FOFS(mass), F_INT},
+	{"volume", FOFS(volume), F_FLOAT},
+	{"attenuation", FOFS(attenuation), F_FLOAT},
+	{"map", FOFS(map), F_LSTRING},
+	{"origin", FOFS(s.origin), F_VECTOR},
+	{"angles", FOFS(s.angles), F_VECTOR},
+	{"angle", FOFS(s.angles), F_ANGLEHACK},
+
+	{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+	{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+	{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+	{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+	{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+	{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+	{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+	{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+	{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+	{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+	{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+	{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+	{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+
+	{"prethink", FOFS(prethink), F_IGNORE, FFL_NOSPAWN},
+	{"think", FOFS(think), F_IGNORE, FFL_NOSPAWN},
+	{"blocked", FOFS(blocked), F_IGNORE, FFL_NOSPAWN},
+	{"touch", FOFS(touch), F_IGNORE, FFL_NOSPAWN},
+	{"use", FOFS(use), F_IGNORE, FFL_NOSPAWN},
+	{"pain", FOFS(pain), F_IGNORE, FFL_NOSPAWN},
+	{"die", FOFS(die), F_IGNORE, FFL_NOSPAWN},
+
+	{"stand", FOFS(monsterinfo.stand), F_IGNORE, FFL_NOSPAWN},
+	{"idle", FOFS(monsterinfo.idle), F_IGNORE, FFL_NOSPAWN},
+	{"search", FOFS(monsterinfo.search), F_IGNORE, FFL_NOSPAWN},
+	{"walk", FOFS(monsterinfo.walk), F_IGNORE, FFL_NOSPAWN},
+	{"run", FOFS(monsterinfo.run), F_IGNORE, FFL_NOSPAWN},
+	{"dodge", FOFS(monsterinfo.dodge), F_IGNORE, FFL_NOSPAWN},
+	{"attack", FOFS(monsterinfo.attack), F_IGNORE, FFL_NOSPAWN},
+	{"melee", FOFS(monsterinfo.melee), F_IGNORE, FFL_NOSPAWN},
+	{"sight", FOFS(monsterinfo.sight), F_IGNORE, FFL_NOSPAWN},
+	{"checkattack", FOFS(monsterinfo.checkattack), F_IGNORE, FFL_NOSPAWN},
+	{"currentmove", FOFS(monsterinfo.currentmove), F_IGNORE, FFL_NOSPAWN},
+
+	{"endfunc", FOFS(moveinfo.endfunc), F_IGNORE, FFL_NOSPAWN},
+
+	// temp spawn vars -- only valid when the spawn function is called
+	{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+	{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+	{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+	{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+	{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+	{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+
+//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves
+	{"item", FOFS(item), F_ITEM},
+
+	{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+	{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+	{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+	{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+	{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+
+	{0, 0, 0, 0}
+
+};
+
+// -------- just for savegames ----------
+// all pointer fields should be listed here, or savegames
+// won't work properly (they will crash and burn).
+// this wasn't just tacked on to the fields array, because
+// these don't need names, we wouldn't want map fields using
+// some of these, and if one were accidentally present twice
+// it would double swizzle (fuck) the pointer.
+
+field_t		savefields[] =
+{
+	{"", FOFS(classname), F_LSTRING},
+	{"", FOFS(target), F_LSTRING},
+	{"", FOFS(targetname), F_LSTRING},
+	{"", FOFS(killtarget), F_LSTRING},
+	{"", FOFS(team), F_LSTRING},
+	{"", FOFS(pathtarget), F_LSTRING},
+	{"", FOFS(deathtarget), F_LSTRING},
+	{"", FOFS(combattarget), F_LSTRING},
+	{"", FOFS(model), F_LSTRING},
+	{"", FOFS(map), F_LSTRING},
+	{"", FOFS(message), F_LSTRING},
+
+	{"", FOFS(client), F_CLIENT},
+	{"", FOFS(item), F_ITEM},
+
+	{"", FOFS(goalentity), F_EDICT},
+	{"", FOFS(movetarget), F_EDICT},
+	{"", FOFS(enemy), F_EDICT},
+	{"", FOFS(oldenemy), F_EDICT},
+	{"", FOFS(activator), F_EDICT},
+	{"", FOFS(groundentity), F_EDICT},
+	{"", FOFS(teamchain), F_EDICT},
+	{"", FOFS(teammaster), F_EDICT},
+	{"", FOFS(owner), F_EDICT},
+	{"", FOFS(mynoise), F_EDICT},
+	{"", FOFS(mynoise2), F_EDICT},
+	{"", FOFS(target_ent), F_EDICT},
+	{"", FOFS(chain), F_EDICT},
+
+	{NULL, 0, F_INT}
+};
+
+field_t		levelfields[] =
+{
+	{"", LLOFS(changemap), F_LSTRING},
+
+	{"", LLOFS(sight_client), F_EDICT},
+	{"", LLOFS(sight_entity), F_EDICT},
+	{"", LLOFS(sound_entity), F_EDICT},
+	{"", LLOFS(sound2_entity), F_EDICT},
+
+	{NULL, 0, F_INT}
+};
+
+field_t		clientfields[] =
+{
+	{"", CLOFS(pers.weapon), F_ITEM},
+	{"", CLOFS(pers.lastweapon), F_ITEM},
+	{"", CLOFS(newweapon), F_ITEM},
+
+	{NULL, 0, F_INT}
+};
+
+
+//== crbot cvars
+cvar_t  *bot_skin;
+cvar_t  *bot_model;
+cvar_t  *bot_team;
+cvar_t  *bot_skill;
+cvar_t  *bot_menu;
+cvar_t  *bot_chat;
+cvar_t  *obituary_msgs;
+cvar_t  *bot_taunt;
+cvar_t  *map_cycle;
+cvar_t  *game_path;
+cvar_t  *base_path;
+cvar_t  *no_tech;
+cvar_t  *no_hook;
+cvar_t  *bot_debug;
+//==
+
+/*
+============
+InitGame
+
+This will be called when the dll is first loaded, which
+only happens when a new game is started or a save game
+is loaded.
+============
+*/
+void InitGame (void)
+{
+	gi.dprintf ("==== InitGame ====\n");
+
+	gun_x = gi.cvar ("gun_x", "0", 0);
+	gun_y = gi.cvar ("gun_y", "0", 0);
+	gun_z = gi.cvar ("gun_z", "0", 0);
+
+	//FIXME: sv_ prefix is wrong for these
+	sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+	sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+	sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+	sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+
+	// noset vars
+	dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+	// latched vars
+	sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+	gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+	gi.cvar ("gamedate", "Nov 30 1997" , CVAR_SERVERINFO | CVAR_LATCH);
+
+	maxclients = gi.cvar ("maxclients", "12", CVAR_SERVERINFO | CVAR_LATCH);
+	deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+	coop = gi.cvar ("coop", "0", CVAR_LATCH);
+	skill = gi.cvar ("skill", "1", CVAR_LATCH);
+	maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+
+//ZOID
+//This game.dll only supports deathmatch
+	if (!deathmatch->value) {
+		gi.dprintf("Forcing deathmatch.\n");
+		gi.cvar_set("deathmatch", "1");
+	}
+	//force coop off
+	if (coop->value)
+		gi.cvar_set("coop", "0");
+//ZOID
+
+
+	// change anytime vars
+	dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+	fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+	timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+//ZOID
+	capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO);
+//ZOID
+ 	password = gi.cvar ("password", "", CVAR_USERINFO);
+
+	g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+
+	run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+	run_roll = gi.cvar ("run_roll", "0.005", 0);
+	bob_up  = gi.cvar ("bob_up", "0.005", 0);
+	bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+	bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+//== crbot cvars
+	bot_skin  = gi.cvar ( "bot_skin", "male/viper", CVAR_ARCHIVE );
+	bot_model = gi.cvar ( "bot_model", "male", CVAR_ARCHIVE );
+	bot_team  = gi.cvar ( "bot_team", "0", 0/*CVAR_ARCHIVE*/ );
+	bot_skill = gi.cvar ( "bot_skill", "0", CVAR_ARCHIVE );
+	bot_chat  = gi.cvar ( "bot_chat", "1", CVAR_ARCHIVE );
+	bot_taunt = gi.cvar ( "bot_taunt", "1", CVAR_ARCHIVE );
+
+	map_cycle = gi.cvar ( "map_cycle", "0", CVAR_ARCHIVE );
+	bot_menu  = gi.cvar ( "bot_menu", "1", CVAR_ARCHIVE );
+	obituary_msgs = gi.cvar ( "obituary_msgs", "1", CVAR_ARCHIVE );
+
+	no_tech = gi.cvar ( "no_tech", "1", CVAR_ARCHIVE );
+	no_hook = gi.cvar ( "no_hook", "1", CVAR_ARCHIVE );
+	game_path = gi.cvar ( "game", "", CVAR_NOSET | CVAR_LATCH );
+	base_path = gi.cvar ( "basedir", "", CVAR_NOSET );
+
+	bot_debug = gi.cvar ( "bot_debug", "0", 0 );
+//==
+
+	// items
+	InitItems ();
+
+	Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "");
+
+	Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "");
+
+	// initialize all entities for this game
+	game.maxentities = maxentities->value;
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+	globals.max_edicts = game.maxentities;
+
+	// initialize all clients for this game
+	game.maxclients = maxclients->value;
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	globals.num_edicts = game.maxclients+1;
+
+//ZOID
+	CTFInit();
+//ZOID
+
+    cr_init_globals();
+}
+
+//=========================================================
+
+void WriteField1 (FILE */*f*/, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+	case F_GSTRING:
+		if ( *(char **)p )
+			len = strlen(*(char **)p) + 1;
+		else
+			len = 0;
+		*(int *)p = len;
+		break;
+	case F_EDICT:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(edict_t **)p - g_edicts;
+		*(int *)p = index;
+		break;
+	case F_CLIENT:
+		if ( *(gclient_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gclient_t **)p - game.clients;
+		*(int *)p = index;
+		break;
+	case F_ITEM:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gitem_t **)p - itemlist;
+		*(int *)p = index;
+		break;
+
+	default:
+		gi.error ("WriteEdict: unknown field type");
+	}
+}
+
+void WriteField2 (FILE *f, field_t *field, byte *base)
+{
+	int			len;
+	void		*p;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_LSTRING:
+	case F_GSTRING:
+		if ( *(char **)p )
+		{
+			len = strlen(*(char **)p) + 1;
+			fwrite (*(char **)p, len, 1, f);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+void ReadField (FILE *f, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+		len = *(int *)p;
+		if (!len)
+			*(char **)p = NULL;
+		else
+		{
+			*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
+			fread (*(char **)p, len, 1, f);
+		}
+		break;
+	case F_GSTRING:
+		len = *(int *)p;
+		if (!len)
+			*(char **)p = NULL;
+		else
+		{
+			*(char **)p = gi.TagMalloc (len, TAG_GAME);
+			fread (*(char **)p, len, 1, f);
+		}
+		break;
+	case F_EDICT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(edict_t **)p = NULL;
+		else
+			*(edict_t **)p = &g_edicts[index];
+		break;
+	case F_CLIENT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gclient_t **)p = NULL;
+		else
+			*(gclient_t **)p = &game.clients[index];
+		break;
+	case F_ITEM:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gitem_t **)p = NULL;
+		else
+			*(gitem_t **)p = &itemlist[index];
+		break;
+
+	default:
+		gi.error ("ReadEdict: unknown field type");
+	}
+}
+
+//=========================================================
+
+/*
+==============
+WriteClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+	gclient_t	temp;
+	
+	// all of the ints, floats, and vectors stay as they are
+	temp = *client;
+
+	// change the pointers to lengths or indexes
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)client);
+	}
+}
+
+/*
+==============
+ReadClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+
+	fread (client, sizeof(*client), 1, f);
+
+	for (field=clientfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)client);
+	}
+}
+
+/*
+============
+WriteGame
+
+This will be called whenever the game goes to a new level,
+and when the user explicitly saves the game.
+
+Game information include cross level data, like multi level
+triggers, help computer info, and all client states.
+
+A single player death will automatically restore from the
+last save position.
+============
+*/
+void WriteGame (char *filename, qboolean autosave)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	if (!autosave)
+		SaveClientData ();
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	memset (str, 0, sizeof(str));
+	strcpy (str, "Nov 30 1997");
+	fwrite (str, sizeof(str), 1, f);
+
+	game.autosaved = autosave;
+	fwrite (&game, sizeof(game), 1, f);
+	game.autosaved = false;
+
+	for (i=0 ; i<game.maxclients ; i++)
+		WriteClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+void ReadGame (char *filename)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	gi.FreeTags (TAG_GAME);
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	fread (str, sizeof(str), 1, f);
+	if (strcmp (str, "Nov 30 1997"))
+	{
+		fclose (f);
+		gi.error ("Savegame from an older version.\n");
+	}
+
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+
+	fread (&game, sizeof(game), 1, f);
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	for (i=0 ; i<game.maxclients ; i++)
+		ReadClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+//==========================================================
+
+
+/*
+==============
+WriteEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+	edict_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = *ent;
+
+	// change the pointers to lengths or indexes
+	for (field=savefields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=savefields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)ent);
+	}
+
+}
+
+/*
+==============
+WriteLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteLevelLocals (FILE *f)
+{
+	field_t		*field;
+	level_locals_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = level;
+
+	// change the pointers to lengths or indexes
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)&level);
+	}
+}
+
+
+/*
+==============
+ReadEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+
+	fread (ent, sizeof(*ent), 1, f);
+
+	for (field=savefields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)ent);
+	}
+}
+
+/*
+==============
+ReadLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadLevelLocals (FILE *f)
+{
+	field_t		*field;
+
+	fread (&level, sizeof(level), 1, f);
+
+	for (field=levelfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)&level);
+	}
+}
+
+/*
+=================
+WriteLevel
+
+=================
+*/
+void WriteLevel (char *filename)
+{
+	int		i;
+	edict_t	*ent;
+	FILE	*f;
+	void	*base;
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// write out edict size for checking
+	i = sizeof(edict_t);
+	fwrite (&i, sizeof(i), 1, f);
+
+	// write out a function pointer for checking
+	base = (void *)InitGame;
+	fwrite (&base, sizeof(base), 1, f);
+
+	// write out level_locals_t
+	WriteLevelLocals (f);
+
+	// write out all the entities
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+		if (!ent->inuse)
+			continue;
+		fwrite (&i, sizeof(i), 1, f);
+		WriteEdict (f, ent);
+	}
+	i = -1;
+	fwrite (&i, sizeof(i), 1, f);
+
+	fclose (f);
+}
+
+
+/*
+=================
+ReadLevel
+
+SpawnEntities will allready have been called on the
+level the same way it was when the level was saved.
+
+That is necessary to get the baselines
+set up identically.
+
+The server will have cleared all of the world links before
+calling ReadLevel.
+
+No clients are connected yet.
+=================
+*/
+void ReadLevel (char *filename)
+{
+	int		entnum;
+	FILE	*f;
+	int		i;
+	void	*base;
+	edict_t	*ent;
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// free any dynamic memory allocated by loading the level
+	// base state
+	gi.FreeTags (TAG_LEVEL);
+
+	// wipe all the entities
+	memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
+	globals.num_edicts = maxclients->value+1;
+
+	// check edict size
+	fread (&i, sizeof(i), 1, f);
+	if (i != sizeof(edict_t))
+	{
+		fclose (f);
+		gi.error ("ReadLevel: mismatched edict size");
+	}
+
+	// check function pointer base address
+	fread (&base, sizeof(base), 1, f);
+	gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
+
+	// load the level locals
+	ReadLevelLocals (f);
+
+	// load all the entities
+	while (1)
+	{
+		if (fread (&entnum, sizeof(entnum), 1, f) != 1)
+		{
+			fclose (f);
+			gi.error ("ReadLevel: failed to read entnum");
+		}
+		if (entnum == -1)
+			break;
+		if (entnum >= globals.num_edicts)
+			globals.num_edicts = entnum+1;
+
+		ent = &g_edicts[entnum];
+		ReadEdict (f, ent);
+
+		// let the server rebuild world links for this ent
+		memset (&ent->area, 0, sizeof(ent->area));
+		gi.linkentity (ent);
+	}
+
+	fclose (f);
+
+	// mark all clients as unconnected
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = &g_edicts[i+1];
+		ent->client = game.clients + i;
+		ent->client->pers.connected = false;
+	}
+
+	// do any load time things at this point
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+
+		if (!ent->inuse)
+			continue;
+
+		// fire any cross-level triggers
+		if (ent->classname)
+			if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+				ent->nextthink = level.time + ent->delay;
+	}
+}
--- /dev/null
+++ b/crbot/g_spawn.c
@@ -1,0 +1,989 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+typedef struct
+{
+	char	*name;
+	void	(*spawn)(edict_t *ent);
+} spawn_t;
+
+
+void SP_item_health (edict_t *self);
+void SP_item_health_small (edict_t *self);
+void SP_item_health_large (edict_t *self);
+void SP_item_health_mega (edict_t *self);
+
+void SP_info_player_start (edict_t *ent);
+void SP_info_player_deathmatch (edict_t *ent);
+void SP_info_player_coop (edict_t *ent);
+void SP_info_player_intermission (edict_t *ent);
+
+void SP_func_plat (edict_t *ent);
+void SP_func_rotating (edict_t *ent);
+void SP_func_button (edict_t *ent);
+void SP_func_door (edict_t *ent);
+void SP_func_door_secret (edict_t *ent);
+void SP_func_door_rotating (edict_t *ent);
+void SP_func_water (edict_t *ent);
+void SP_func_train (edict_t *ent);
+void SP_func_conveyor (edict_t *self);
+void SP_func_wall (edict_t *self);
+void SP_func_object (edict_t *self);
+void SP_func_explosive (edict_t *self);
+void SP_func_timer (edict_t *self);
+void SP_func_areaportal (edict_t *ent);
+void SP_func_clock (edict_t *ent);
+void SP_func_killbox (edict_t *ent);
+
+void SP_trigger_always (edict_t *ent);
+void SP_trigger_once (edict_t *ent);
+void SP_trigger_multiple (edict_t *ent);
+void SP_trigger_relay (edict_t *ent);
+void SP_trigger_push (edict_t *ent);
+void SP_trigger_hurt (edict_t *ent);
+void SP_trigger_key (edict_t *ent);
+void SP_trigger_counter (edict_t *ent);
+void SP_trigger_elevator (edict_t *ent);
+void SP_trigger_gravity (edict_t *ent);
+void SP_trigger_monsterjump (edict_t *ent);
+
+void SP_target_temp_entity (edict_t *ent);
+void SP_target_speaker (edict_t *ent);
+void SP_target_explosion (edict_t *ent);
+void SP_target_changelevel (edict_t *ent);
+void SP_target_secret (edict_t *ent);
+void SP_target_goal (edict_t *ent);
+void SP_target_splash (edict_t *ent);
+void SP_target_spawner (edict_t *ent);
+void SP_target_blaster (edict_t *ent);
+void SP_target_crosslevel_trigger (edict_t *ent);
+void SP_target_crosslevel_target (edict_t *ent);
+void SP_target_laser (edict_t *self);
+void SP_target_help (edict_t *ent);
+void SP_target_actor (edict_t *ent);
+void SP_target_lightramp (edict_t *self);
+void SP_target_earthquake (edict_t *ent);
+void SP_target_character (edict_t *ent);
+void SP_target_string (edict_t *ent);
+
+void SP_worldspawn (edict_t *ent);
+void SP_viewthing (edict_t *ent);
+
+void SP_light (edict_t *self);
+void SP_light_mine1 (edict_t *ent);
+void SP_light_mine2 (edict_t *ent);
+void SP_info_null (edict_t *self);
+void SP_info_notnull (edict_t *self);
+void SP_path_corner (edict_t *self);
+void SP_point_combat (edict_t *self);
+
+void SP_misc_explobox (edict_t *self);
+void SP_misc_banner (edict_t *self);
+void SP_misc_satellite_dish (edict_t *self);
+void SP_misc_actor (edict_t *self);
+void SP_misc_gib_arm (edict_t *self);
+void SP_misc_gib_leg (edict_t *self);
+void SP_misc_gib_head (edict_t *self);
+void SP_misc_insane (edict_t *self);
+void SP_misc_deadsoldier (edict_t *self);
+void SP_misc_viper (edict_t *self);
+void SP_misc_viper_bomb (edict_t *self);
+void SP_misc_bigviper (edict_t *self);
+void SP_misc_strogg_ship (edict_t *self);
+void SP_misc_teleporter (edict_t *self);
+void SP_misc_teleporter_dest (edict_t *self);
+void SP_misc_blackhole (edict_t *self);
+void SP_misc_eastertank (edict_t *self);
+void SP_misc_easterchick (edict_t *self);
+void SP_misc_easterchick2 (edict_t *self);
+
+void SP_monster_berserk (edict_t *self);
+void SP_monster_gladiator (edict_t *self);
+void SP_monster_gunner (edict_t *self);
+void SP_monster_infantry (edict_t *self);
+void SP_monster_soldier_light (edict_t *self);
+void SP_monster_soldier (edict_t *self);
+void SP_monster_soldier_ss (edict_t *self);
+void SP_monster_tank (edict_t *self);
+void SP_monster_medic (edict_t *self);
+void SP_monster_flipper (edict_t *self);
+void SP_monster_chick (edict_t *self);
+void SP_monster_parasite (edict_t *self);
+void SP_monster_flyer (edict_t *self);
+void SP_monster_brain (edict_t *self);
+void SP_monster_floater (edict_t *self);
+void SP_monster_hover (edict_t *self);
+void SP_monster_mutant (edict_t *self);
+void SP_monster_supertank (edict_t *self);
+void SP_monster_boss2 (edict_t *self);
+void SP_monster_jorg (edict_t *self);
+void SP_monster_boss3_stand (edict_t *self);
+
+void SP_monster_commander_body (edict_t *self);
+
+void SP_turret_breach (edict_t *self);
+void SP_turret_base (edict_t *self);
+void SP_turret_driver (edict_t *self);
+
+
+spawn_t	spawns[] = {
+	{"item_health", SP_item_health},
+	{"item_health_small", SP_item_health_small},
+	{"item_health_large", SP_item_health_large},
+	{"item_health_mega", SP_item_health_mega},
+
+	{"info_player_start", SP_info_player_start},
+	{"info_player_deathmatch", SP_info_player_deathmatch},
+	{"info_player_coop", SP_info_player_coop},
+	{"info_player_intermission", SP_info_player_intermission},
+//ZOID
+	{"info_player_team1", SP_info_player_team1},
+	{"info_player_team2", SP_info_player_team2},
+//ZOID
+
+	{"func_plat", SP_func_plat},
+	{"func_button", SP_func_button},
+	{"func_door", SP_func_door},
+	{"func_door_secret", SP_func_door_secret},
+	{"func_door_rotating", SP_func_door_rotating},
+	{"func_rotating", SP_func_rotating},
+	{"func_train", SP_func_train},
+	{"func_water", SP_func_water},
+	{"func_conveyor", SP_func_conveyor},
+	{"func_areaportal", SP_func_areaportal},
+	{"func_clock", SP_func_clock},
+	{"func_wall", SP_func_wall},
+	{"func_object", SP_func_object},
+	{"func_timer", SP_func_timer},
+	{"func_explosive", SP_func_explosive},
+	{"func_killbox", SP_func_killbox},
+
+	{"trigger_always", SP_trigger_always},
+	{"trigger_once", SP_trigger_once},
+	{"trigger_multiple", SP_trigger_multiple},
+	{"trigger_relay", SP_trigger_relay},
+	{"trigger_push", SP_trigger_push},
+	{"trigger_hurt", SP_trigger_hurt},
+	{"trigger_key", SP_trigger_key},
+	{"trigger_counter", SP_trigger_counter},
+	{"trigger_elevator", SP_trigger_elevator},
+	{"trigger_gravity", SP_trigger_gravity},
+	{"trigger_monsterjump", SP_trigger_monsterjump},
+
+	{"target_temp_entity", SP_target_temp_entity},
+	{"target_speaker", SP_target_speaker},
+	{"target_explosion", SP_target_explosion},
+	{"target_changelevel", SP_target_changelevel},
+	{"target_secret", SP_target_secret},
+	{"target_goal", SP_target_goal},
+	{"target_splash", SP_target_splash},
+	{"target_spawner", SP_target_spawner},
+	{"target_blaster", SP_target_blaster},
+	{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
+	{"target_crosslevel_target", SP_target_crosslevel_target},
+	{"target_laser", SP_target_laser},
+	{"target_help", SP_target_help},
+/* remove monster code
+	{"target_actor", SP_target_actor},
+*/
+	{"target_lightramp", SP_target_lightramp},
+	{"target_earthquake", SP_target_earthquake},
+	{"target_character", SP_target_character},
+	{"target_string", SP_target_string},
+
+	{"worldspawn", SP_worldspawn},
+	{"viewthing", SP_viewthing},
+
+	{"light", SP_light},
+	{"light_mine1", SP_light_mine1},
+	{"light_mine2", SP_light_mine2},
+	{"info_null", SP_info_null},
+	{"func_group", SP_info_null},
+	{"info_notnull", SP_info_notnull},
+	{"path_corner", SP_path_corner},
+	{"point_combat", SP_point_combat},
+
+	{"misc_explobox", SP_misc_explobox},
+	{"misc_banner", SP_misc_banner},
+//ZOID
+	{"misc_ctf_banner", SP_misc_ctf_banner},
+	{"misc_ctf_small_banner", SP_misc_ctf_small_banner},
+//ZOID
+	{"misc_satellite_dish", SP_misc_satellite_dish},
+/* remove monster code
+	{"misc_actor", SP_misc_actor},
+*/
+	{"misc_gib_arm", SP_misc_gib_arm},
+	{"misc_gib_leg", SP_misc_gib_leg},
+	{"misc_gib_head", SP_misc_gib_head},
+/* remove monster code
+	{"misc_insane", SP_misc_insane},
+*/
+	{"misc_deadsoldier", SP_misc_deadsoldier},
+	{"misc_viper", SP_misc_viper},
+	{"misc_viper_bomb", SP_misc_viper_bomb},
+	{"misc_bigviper", SP_misc_bigviper},
+	{"misc_strogg_ship", SP_misc_strogg_ship},
+	{"misc_teleporter", SP_misc_teleporter},
+	{"misc_teleporter_dest", SP_misc_teleporter_dest},
+//ZOID
+	{"trigger_teleport", SP_trigger_teleport},
+	{"info_teleport_destination", SP_info_teleport_destination},
+//ZOID
+	{"misc_blackhole", SP_misc_blackhole},
+	{"misc_eastertank", SP_misc_eastertank},
+	{"misc_easterchick", SP_misc_easterchick},
+	{"misc_easterchick2", SP_misc_easterchick2},
+
+/* remove monster code
+	{"monster_berserk", SP_monster_berserk},
+	{"monster_gladiator", SP_monster_gladiator},
+	{"monster_gunner", SP_monster_gunner},
+	{"monster_infantry", SP_monster_infantry},
+	{"monster_soldier_light", SP_monster_soldier_light},
+	{"monster_soldier", SP_monster_soldier},
+	{"monster_soldier_ss", SP_monster_soldier_ss},
+	{"monster_tank", SP_monster_tank},
+	{"monster_tank_commander", SP_monster_tank},
+	{"monster_medic", SP_monster_medic},
+	{"monster_flipper", SP_monster_flipper},
+	{"monster_chick", SP_monster_chick},
+	{"monster_parasite", SP_monster_parasite},
+	{"monster_flyer", SP_monster_flyer},
+	{"monster_brain", SP_monster_brain},
+	{"monster_floater", SP_monster_floater},
+	{"monster_hover", SP_monster_hover},
+	{"monster_mutant", SP_monster_mutant},
+	{"monster_supertank", SP_monster_supertank},
+	{"monster_boss2", SP_monster_boss2},
+	{"monster_boss3_stand", SP_monster_boss3_stand},
+	{"monster_jorg", SP_monster_jorg},
+
+	{"monster_commander_body", SP_monster_commander_body},
+
+	{"turret_breach", SP_turret_breach},
+	{"turret_base", SP_turret_base},
+	{"turret_driver", SP_turret_driver},
+*/
+
+	{NULL, NULL}
+};
+
+/*
+===============
+ED_CallSpawn
+
+Finds the spawn function for the entity and calls it
+===============
+*/
+void ED_CallSpawn (edict_t *ent)
+{
+	spawn_t	*s;
+	gitem_t	*item;
+	int		i;
+
+	if (!ent->classname)
+	{
+		gi.dprintf ("ED_CallSpawn: NULL classname\n");
+		return;
+	}
+
+	// check item spawn functions
+	for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
+	{
+		if (!item->classname)
+			continue;
+		if (!strcmp(item->classname, ent->classname))
+		{	// found it
+			SpawnItem (ent, item);
+			return;
+		}
+	}
+
+	// check normal spawn functions
+	for (s=spawns ; s->name ; s++)
+	{
+		if (!strcmp(s->name, ent->classname))
+		{	// found it
+			s->spawn (ent);
+			return;
+		}
+	}
+	gi.dprintf ("%s doesn't have a spawn function\n", ent->classname);
+}
+
+/*
+=============
+ED_NewString
+=============
+*/
+char *ED_NewString (char *string)
+{
+	char	*newb, *new_p;
+	int		i,l;
+	
+	l = strlen(string) + 1;
+
+	newb = gi.TagMalloc (l, TAG_LEVEL);
+
+	new_p = newb;
+
+	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;
+}
+
+
+
+
+/*
+===============
+ED_ParseField
+
+Takes a key/value pair and sets the binary values
+in an edict
+===============
+*/
+void ED_ParseField (char *key, char *value, edict_t *ent)
+{
+	field_t	*f;
+	byte	*b;
+	float	v;
+	vec3_t	vec;
+
+	for (f=fields ; f->name ; f++)
+	{
+		if (!cistrcmp(f->name, key))
+		{	// found it
+			if (f->flags & FFL_SPAWNTEMP)
+				b = (byte *)&st;
+			else
+				b = (byte *)ent;
+
+			switch (f->type)
+			{
+			case F_LSTRING:
+				*(char **)(b+f->ofs) = ED_NewString (value);
+				break;
+			case F_VECTOR:
+				sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
+				((float *)(b+f->ofs))[0] = vec[0];
+				((float *)(b+f->ofs))[1] = vec[1];
+				((float *)(b+f->ofs))[2] = vec[2];
+				break;
+			case F_INT:
+				*(int *)(b+f->ofs) = atoi(value);
+				break;
+			case F_FLOAT:
+				*(float *)(b+f->ofs) = atof(value);
+				break;
+			case F_ANGLEHACK:
+				v = atof(value);
+				((float *)(b+f->ofs))[0] = 0;
+				((float *)(b+f->ofs))[1] = v;
+				((float *)(b+f->ofs))[2] = 0;
+				break;
+			case F_IGNORE:
+				break;
+			default:
+				break;
+			}
+			return;
+		}
+	}
+	gi.dprintf ("%s is not a field\n", key);
+}
+
+/*
+====================
+ED_ParseEdict
+
+Parses an edict out of the given string, returning the new position
+ed should be a properly initialized empty edict.
+====================
+*/
+char *ED_ParseEdict (char *data, edict_t *ent)
+{
+	qboolean	init;
+	char		keyname[256];
+	char		*com_token;
+
+	init = false;
+	memset (&st, 0, sizeof(st));
+
+// go through all the dictionary pairs
+	while (1)
+	{	
+	// parse key
+		com_token = COM_Parse (&data);
+		if (com_token[0] == '}')
+			break;
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		strncpy (keyname, com_token, sizeof(keyname)-1);
+		
+	// parse value	
+		com_token = COM_Parse (&data);
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		if (com_token[0] == '}')
+			gi.error ("ED_ParseEntity: closing brace without data");
+
+		init = true;	
+
+	// keynames with a leading underscore are used for utility comments,
+	// and are immediately discarded by quake
+		if (keyname[0] == '_')
+			continue;
+
+		ED_ParseField (keyname, com_token, ent);
+	}
+
+	if (!init)
+		memset (ent, 0, sizeof(*ent));
+
+	return data;
+}
+
+
+/*
+================
+G_FindTeams
+
+Chain together all entities with a matching team field.
+
+All but the first will have the FL_TEAMSLAVE flag set.
+All but the last will have the teamchain field set to the next one
+================
+*/
+void G_FindTeams (void)
+{
+	edict_t	*e, *e2, *chain;
+	int		i, j;
+	int		c, c2;
+
+	c = 0;
+	c2 = 0;
+	for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->team)
+			continue;
+		if (e->flags & FL_TEAMSLAVE)
+			continue;
+		chain = e;
+		e->teammaster = e;
+		c++;
+		c2++;
+		for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++)
+		{
+			if (!e2->inuse)
+				continue;
+			if (!e2->team)
+				continue;
+			if (e2->flags & FL_TEAMSLAVE)
+				continue;
+			if (!strcmp(e->team, e2->team))
+			{
+				c2++;
+				chain->teamchain = e2;
+				e2->teammaster = e;
+				chain = e2;
+				e2->flags |= FL_TEAMSLAVE;
+			}
+		}
+	}
+
+	gi.dprintf ("%i teams with %i entities\n", c, c2);
+}
+
+/*
+==============
+SpawnEntities
+
+Creates a server's entity / program execution context by
+parsing textual entity definitions out of an ent file.
+==============
+*/
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint)
+{
+	edict_t		*ent;
+	int			inhibit;
+	char		*com_token;
+	int			i;
+	float		skill_level;
+
+    qboolean    b_init_bot;
+    b_init_bot = entities!=NULL && *entities!='\0';
+
+	skill_level = floor (skill->value);
+	if (skill_level < 0)
+		skill_level = 0;
+	if (skill_level > 3)
+		skill_level = 3;
+	if (skill->value != skill_level)
+		gi.cvar_forceset("skill", va("%f", skill_level));
+
+	SaveClientData ();
+
+	gi.FreeTags (TAG_LEVEL);
+
+	memset (&level, 0, sizeof(level));
+	memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
+
+	strncpy (level.mapname, mapname, sizeof(level.mapname)-1);
+	strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1);
+
+	// set client fields on player ents
+	for (i=0 ; i<game.maxclients ; i++)
+		g_edicts[i+1].client = game.clients + i;
+
+	ent = NULL;
+	inhibit = 0;
+
+// parse ents
+	while (1)
+	{
+		// parse the opening brace	
+		com_token = COM_Parse (&entities);
+		if (!entities)
+			break;
+		if (com_token[0] != '{')
+			gi.error ("ED_LoadFromFile: found %s when expecting {",com_token);
+
+		if (!ent)
+			ent = g_edicts;
+		else
+			ent = G_Spawn ();
+		entities = ED_ParseEdict (entities, ent);
+		
+		// yet another map hack
+		if (!cistrcmp(level.mapname, "command") && !cistrcmp(ent->classname, "trigger_once") && !cistrcmp(ent->model, "*27"))
+			ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
+
+		// remove things (except the world) from different skill levels or deathmatch
+		if (ent != g_edicts)
+		{
+			if (deathmatch->value)
+			{
+				if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
+				{
+					G_FreeEdict (ent);	
+					inhibit++;
+					continue;
+				}
+			}
+			else
+			{
+				if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */
+					((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+					((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+					(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
+					)
+					{
+						G_FreeEdict (ent);	
+						inhibit++;
+						continue;
+					}
+			}
+
+			ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH);
+		}
+
+		ED_CallSpawn (ent);
+	}	
+
+	gi.dprintf ("%i entities inhibited\n", inhibit);
+
+	G_FindTeams ();
+
+	PlayerTrail_Init ();
+
+//ZOID
+	CTFSetupTechSpawn();
+//ZOID
+
+    if (b_init_bot) cr_init();
+}
+
+
+//===================================================================
+
+/*
+	// cursor positioning
+	xl <value>
+	xr <value>
+	yb <value>
+	yt <value>
+	xv <value>
+	yv <value>
+
+	// drawing
+	statpic <name>
+	pic <stat>
+	num <fieldwidth> <stat>
+	string <stat>
+
+	// control
+	if <stat>
+	ifeq <stat> <value>
+	ifbit <stat> <value>
+	endif
+
+*/
+
+char *single_statusbar = 
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	262 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+;
+
+char *dm_statusbar =
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	246 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+
+//  frags
+"xr	-50 "
+"yt 2 "
+"num 3 14"
+;
+
+
+/*QUAKED worldspawn (0 0 0) ?
+
+Only used for the world.
+"sky"	environment map name
+"skyaxis"	vector axis for rotating sky
+"skyrotate"	speed of rotation in degrees/second
+"sounds"	music cd track number
+"gravity"	800 is default gravity
+"message"	text to print at user logon
+*/
+void SP_worldspawn (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	ent->inuse = true;			// since the world doesn't use G_Spawn()
+	ent->s.modelindex = 1;		// world model is always index 1
+
+	//---------------
+
+	// reserve some spots for dead player bodies for coop / deathmatch
+	InitBodyQue ();
+
+	// set configstrings for items
+	SetItemNames ();
+
+	if (st.nextmap)
+		strcpy (level.nextmap, st.nextmap);
+
+	// make some data visible to the server
+
+	if (ent->message && ent->message[0])
+	{
+		gi.configstring (CS_NAME, ent->message);
+		strncpy (level.level_name, ent->message, sizeof(level.level_name));
+	}
+	else
+		strncpy (level.level_name, level.mapname, sizeof(level.level_name));
+
+	if (st.sky && st.sky[0])
+		gi.configstring (CS_SKY, st.sky);
+	else
+		gi.configstring (CS_SKY, "unit1_");
+
+	gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) );
+
+	gi.configstring (CS_SKYAXIS, va("%f %f %f",
+		st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) );
+
+	gi.configstring (CS_CDTRACK, va("%i", ent->sounds) );
+
+	gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) );
+
+	// status bar program
+	if (deathmatch->value)
+//ZOID
+		if (ctf->value) {
+			gi.configstring (CS_STATUSBAR, ctf_statusbar);
+			//precaches
+			gi.imageindex("sbctf1");
+			gi.imageindex("sbctf2");
+			gi.imageindex("i_ctf1");
+			gi.imageindex("i_ctf2");
+			gi.imageindex("i_ctf1d");
+			gi.imageindex("i_ctf2d");
+			gi.imageindex("i_ctf1t");
+			gi.imageindex("i_ctf2t");
+			gi.imageindex("i_ctfj");
+		} else
+//ZOID
+		gi.configstring (CS_STATUSBAR, dm_statusbar);
+	else
+		gi.configstring (CS_STATUSBAR, single_statusbar);
+
+	//---------------
+
+
+	// help icon for statusbar
+	gi.imageindex ("i_help");
+	level.pic_health = gi.imageindex ("i_health");
+	gi.imageindex ("help");
+	gi.imageindex ("field_3");
+
+	if (!st.gravity)
+		gi.cvar_set("sv_gravity", "800");
+	else
+		gi.cvar_set("sv_gravity", st.gravity);
+
+	snd_fry = gi.soundindex ("player/fry.wav");	// standing in lava / slime
+
+	PrecacheItem (FindItem ("Blaster"));
+
+	gi.soundindex ("player/lava1.wav");
+	gi.soundindex ("player/lava2.wav");
+
+	gi.soundindex ("misc/pc_up.wav");
+	gi.soundindex ("misc/talk1.wav");
+
+	gi.soundindex ("misc/udeath.wav");
+
+	// gibs
+	gi.soundindex ("items/respawn1.wav");
+
+	// sexed sounds
+	gi.soundindex ("*death1.wav");
+	gi.soundindex ("*death2.wav");
+	gi.soundindex ("*death3.wav");
+	gi.soundindex ("*death4.wav");
+	gi.soundindex ("*fall1.wav");
+	gi.soundindex ("*fall2.wav");	
+	gi.soundindex ("*gurp1.wav");		// drowning damage
+	gi.soundindex ("*gurp2.wav");	
+	gi.soundindex ("*jump1.wav");		// player jump
+	gi.soundindex ("*pain25_1.wav");
+	gi.soundindex ("*pain25_2.wav");
+	gi.soundindex ("*pain50_1.wav");
+	gi.soundindex ("*pain50_2.wav");
+	gi.soundindex ("*pain75_1.wav");
+	gi.soundindex ("*pain75_2.wav");
+	gi.soundindex ("*pain100_1.wav");
+	gi.soundindex ("*pain100_2.wav");
+
+	// sexed models
+	// THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+	// you can add more, max 15
+	gi.modelindex ("#w_blaster.md2");
+	gi.modelindex ("#w_shotgun.md2");
+	gi.modelindex ("#w_sshotgun.md2");
+	gi.modelindex ("#w_machinegun.md2");
+	gi.modelindex ("#w_chaingun.md2");
+	gi.modelindex ("#a_grenades.md2");
+	gi.modelindex ("#w_glauncher.md2");
+	gi.modelindex ("#w_rlauncher.md2");
+	gi.modelindex ("#w_hyperblaster.md2");
+	gi.modelindex ("#w_railgun.md2");
+	gi.modelindex ("#w_bfg.md2");
+	gi.modelindex ("#w_grapple.md2");
+
+	//-------------------
+
+	gi.soundindex ("player/gasp1.wav");		// gasping for air
+	gi.soundindex ("player/gasp2.wav");		// head breaking surface, not gasping
+
+	gi.soundindex ("player/watr_in.wav");	// feet hitting water
+	gi.soundindex ("player/watr_out.wav");	// feet leaving water
+
+	gi.soundindex ("player/watr_un.wav");	// head going underwater
+	
+	gi.soundindex ("player/u_breath1.wav");
+	gi.soundindex ("player/u_breath2.wav");
+
+	gi.soundindex ("items/pkup.wav");		// bonus item pickup
+	gi.soundindex ("world/land.wav");		// landing thud
+	gi.soundindex ("misc/h2ohit1.wav");		// landing splash
+
+	gi.soundindex ("items/damage.wav");
+	gi.soundindex ("items/protect.wav");
+	gi.soundindex ("items/protect4.wav");
+	gi.soundindex ("weapons/noammo.wav");
+
+	gi.soundindex ("infantry/inflies1.wav");
+
+	sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
+	gi.modelindex ("models/objects/gibs/arm/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone2/tris.md2");
+	gi.modelindex ("models/objects/gibs/chest/tris.md2");
+	gi.modelindex ("models/objects/gibs/skull/tris.md2");
+	gi.modelindex ("models/objects/gibs/head2/tris.md2");
+
+//
+// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
+//
+
+	// 0 normal
+	gi.configstring(CS_LIGHTS+0, "m");
+	
+	// 1 FLICKER (first variety)
+	gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo");
+	
+	// 2 SLOW STRONG PULSE
+	gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+	
+	// 3 CANDLE (first variety)
+	gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+	
+	// 4 FAST STROBE
+	gi.configstring(CS_LIGHTS+4, "mamamamamama");
+	
+	// 5 GENTLE PULSE 1
+	gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+	
+	// 6 FLICKER (second variety)
+	gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno");
+	
+	// 7 CANDLE (second variety)
+	gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm");
+	
+	// 8 CANDLE (third variety)
+	gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+	
+	// 9 SLOW STROBE (fourth variety)
+	gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz");
+	
+	// 10 FLUORESCENT FLICKER
+	gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma");
+
+	// 11 SLOW PULSE NOT FADE TO BLACK
+	gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
+	
+	// styles 32-62 are assigned by the light program for switchable lights
+
+	// 63 testing
+	gi.configstring(CS_LIGHTS+63, "a");
+}
+
--- /dev/null
+++ b/crbot/g_svcmds.c
@@ -1,0 +1,57 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void	Svcmd_Test_f (void)
+{
+	gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
+}
+
+/*
+=================
+ServerCommand
+
+ServerCommand will be called when an "sv" command is issued.
+The game can issue gi.argc() / gi.argv() commands to get the rest
+of the parameters
+=================
+*/
+void	ServerCommand (void)
+{
+	char	*cmd;
+
+	cmd = gi.argv(1);
+	if (cistrcmp (cmd, "test") == 0)
+		Svcmd_Test_f ();
+	else if (cistrcmp (cmd, "addbots") == 0) 
+        {
+        char* p_skill = gi.argv(2);
+        int   i, n = atoi(gi.argv(3)), skill;
+        if (p_skill[1]=='.' && p_skill[2]=='.') {
+          int min_skill, max_skill;
+          p_skill[1]='\0';
+          min_skill=atoi(p_skill);   if (min_skill<1) min_skill=1;   if (min_skill>10) min_skill=10;
+          max_skill=atoi(p_skill+3); if (max_skill<1) max_skill=1;   if (max_skill>10) max_skill=10;
+          if (n<=0) n=1;
+          for (i=0; i<n; i++)
+             SP_crbot( NULL, min_skill+(max_skill-min_skill+1)*qrandom()*0.999f, bot_skin->string, bot_team->value, bot_model->string );
+          }
+        else {
+          skill = atoi(p_skill);
+          if (n<=0) n=1;
+          for (i=0; i<n; i++) SP_crbot( NULL, skill, bot_skin->string, bot_team->value, bot_model->string );
+          }
+        }
+    else if (cistrcmp (cmd, "addbot") == 0) 
+        {
+        if (gi.argv(2) && *gi.argv(2)) 
+          SP_crbot( gi.argv(2), atoi(gi.argv(3)), gi.argv(5), atoi(gi.argv(6)), gi.argv(4) );
+        }
+	else if (cistrcmp (cmd, "killbot") == 0) 
+        cr_kill_bot(gi.argv(2));
+    else
+		gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
+}
+
--- /dev/null
+++ b/crbot/g_target.c
@@ -1,0 +1,796 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
+Fire an origin based temp entity event to the clients.
+"style"		type byte
+*/
+void Use_Target_Tent (edict_t *ent, edict_t *, edict_t *)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (ent->style);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+}
+
+void SP_target_temp_entity (edict_t *ent)
+{
+	ent->use = Use_Target_Tent;
+}
+
+
+//==========================================================
+
+//==========================================================
+
+/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
+"noise"		wav file to play
+"attenuation"
+-1 = none, send to whole level
+1 = normal fighting sounds
+2 = idle sound level
+3 = ambient sound level
+"volume"	0.0 to 1.0
+
+Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.
+
+Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off.
+Multiple identical looping sounds will just increase volume without any speed cost.
+*/
+void Use_Target_Speaker (edict_t *ent, edict_t *, edict_t *)
+{
+	int		chan;
+
+	if (ent->spawnflags & 3)
+	{	// looping sound toggles
+		if (ent->s.sound)
+			ent->s.sound = 0;	// turn it off
+		else
+			ent->s.sound = ent->noise_index;	// start it
+	}
+	else
+	{	// normal sound
+		if (ent->spawnflags & 4)
+			chan = CHAN_VOICE|CHAN_RELIABLE;
+		else
+			chan = CHAN_VOICE;
+		// use a positioned_sound, because this entity won't normally be
+		// sent to any clients because it is invisible
+		gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
+	}
+}
+
+void SP_target_speaker (edict_t *ent)
+{
+	char	buffer[MAX_QPATH];
+
+	if(!st.noise)
+	{
+		gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
+		return;
+	}
+	if (!strstr (st.noise, ".wav"))
+		Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
+	else
+		strncpy (buffer, st.noise, sizeof(buffer));
+	ent->noise_index = gi.soundindex (buffer);
+
+	if (!ent->volume)
+		ent->volume = 1.0;
+
+	if (!ent->attenuation)
+		ent->attenuation = 1.0;
+	else if (ent->attenuation == -1)	// use -1 so 0 defaults to 1
+		ent->attenuation = 0;
+
+	// check for prestarted looping sound
+	if (ent->spawnflags & 1)
+		ent->s.sound = ent->noise_index;
+
+	ent->use = Use_Target_Speaker;
+
+	// must link the entity so we get areas and clusters so
+	// the server can determine who to send updates to
+	gi.linkentity (ent);
+}
+
+
+//==========================================================
+
+void Use_Target_Help (edict_t *ent, edict_t *, edict_t *)
+{
+	if (ent->spawnflags & 1)
+		strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
+	else
+		strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
+
+	game.helpchanged++;
+}
+
+/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
+When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
+*/
+void SP_target_help(edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->message)
+	{
+		gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+	ent->use = Use_Target_Help;
+}
+
+//==========================================================
+
+/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a secret found.
+These are single use targets.
+*/
+void use_target_secret (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_secrets++;
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_secret (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_secret;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_secrets++;
+	// map bug hack
+	if (!cistrcmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
+		ent->message = "You have found a secret area.";
+}
+
+//==========================================================
+
+/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a goal completed.
+These are single use targets.
+*/
+void use_target_goal (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_goals++;
+
+	if (level.found_goals == level.total_goals)
+		gi.configstring (CS_CDTRACK, "0");
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_goal (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_goal;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_goals++;
+}
+
+//==========================================================
+
+
+/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
+Spawns an explosion temporary entity when used.
+
+"delay"		wait this long before going off
+"dmg"		how much radius damage should be done, defaults to 0
+*/
+void target_explosion_explode (edict_t *self)
+{
+	float		save;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	save = self->delay;
+	self->delay = 0;
+	G_UseTargets (self, self->activator);
+	self->delay = save;
+}
+
+void use_target_explosion (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (!self->delay)
+	{
+		target_explosion_explode (self);
+		return;
+	}
+
+	self->think = target_explosion_explode;
+	self->nextthink = level.time + self->delay;
+}
+
+void SP_target_explosion (edict_t *ent)
+{
+	ent->use = use_target_explosion;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
+Changes level to "map" when fired
+*/
+void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
+{
+	if (level.intermissiontime)
+		return;		// allready activated
+
+	if (!deathmatch->value && !coop->value)
+	{
+		if (g_edicts[1].health <= 0)
+			return;
+	}
+
+	// if noexit, do a ton of damage to other
+	if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != WORLD)
+	{
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
+		return;
+	}
+
+	// if multiplayer, let everyone know who hit the exit
+	if (deathmatch->value)
+	{
+		if (activator && activator->client)
+			gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
+	}
+
+	// if going to a new unit, clear cross triggers
+	if (strstr(self->map, "*"))	
+		game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
+
+	BeginIntermission (self);
+}
+
+void SP_target_changelevel (edict_t *ent)
+{
+	if (!ent->map)
+	{
+		gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	// ugly hack because *SOMEBODY* screwed up their map
+   if((cistrcmp(level.mapname, "fact1") == 0) && (cistrcmp(ent->map, "fact3") == 0))
+	   ent->map = "fact3$secret1";
+
+	ent->use = use_target_changelevel;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
+Creates a particle splash effect when used.
+
+Set "sounds" to one of the following:
+  1) sparks
+  2) blue water
+  3) brown water
+  4) slime
+  5) lava
+  6) blood
+
+"count"	how many pixels in the splash
+"dmg"	if set, does a radius damage at this location when it splashes
+		useful for lava/sparks
+*/
+
+void use_target_splash (edict_t *self, edict_t *, edict_t *activator)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPLASH);
+	gi.WriteByte (self->count);
+	gi.WritePosition (self->s.origin);
+	gi.WriteDir (self->movedir);
+	gi.WriteByte (self->sounds);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	if (self->dmg)
+		T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
+}
+
+void SP_target_splash (edict_t *self)
+{
+	self->use = use_target_splash;
+	G_SetMovedir (self->s.angles, self->movedir);
+
+	if (!self->count)
+		self->count = 32;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
+Set target to the type of entity you want spawned.
+Useful for spawning monsters and gibs in the factory levels.
+
+For monsters:
+	Set direction to the facing you want it to have.
+
+For gibs:
+	Set direction if you want it moving and
+	speed how fast it should be moving otherwise it
+	will just be dropped
+*/
+void ED_CallSpawn (edict_t *ent);
+
+void use_target_spawner (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t	*ent;
+
+	ent = G_Spawn();
+	ent->classname = self->target;
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (self->s.angles, ent->s.angles);
+	ED_CallSpawn (ent);
+	gi.unlinkentity (ent);
+	KillBox (ent);
+	gi.linkentity (ent);
+	if (self->speed)
+		VectorCopy (self->movedir, ent->velocity);
+}
+
+void SP_target_spawner (edict_t *self)
+{
+	self->use = use_target_spawner;
+	self->svflags = SVF_NOCLIENT;
+	if (self->speed)
+	{
+		G_SetMovedir (self->s.angles, self->movedir);
+		VectorScale (self->movedir, self->speed, self->movedir);
+	}
+}
+
+//==========================================================
+
+/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
+Fires a blaster bolt in the set direction when triggered.
+
+dmg		default is 15
+speed	default is 1000
+*/
+
+void use_target_blaster (edict_t *self, edict_t *, edict_t *)
+{
+	/*
+	int effect;
+
+	if (self->spawnflags & 2)
+		effect = 0;
+	else if (self->spawnflags & 1)
+		effect = EF_HYPERBLASTER;
+	else
+		effect = EF_BLASTER;
+	*/
+
+	fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
+	gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
+}
+
+void SP_target_blaster (edict_t *self)
+{
+	self->use = use_target_blaster;
+	G_SetMovedir (self->s.angles, self->movedir);
+	self->noise_index = gi.soundindex ("weapons/laser2.wav");
+
+	if (!self->dmg)
+		self->dmg = 15;
+	if (!self->speed)
+		self->speed = 1000;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit.  It is OK to check multiple triggers.  Message, delay, target, and killtarget also work.
+*/
+void trigger_crosslevel_trigger_use (edict_t *self, edict_t *, edict_t *)
+{
+	game.serverflags |= self->spawnflags;
+	G_FreeEdict (self);
+}
+
+void SP_target_crosslevel_trigger (edict_t *self)
+{
+	self->svflags = SVF_NOCLIENT;
+	self->use = trigger_crosslevel_trigger_use;
+}
+
+/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
+killtarget also work.
+
+"delay"		delay before using targets if the trigger has been activated (default 1)
+*/
+void target_crosslevel_target_think (edict_t *self)
+{
+	if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
+	{
+		G_UseTargets (self, self);
+		G_FreeEdict (self);
+	}
+}
+
+void SP_target_crosslevel_target (edict_t *self)
+{
+	if (! self->delay)
+		self->delay = 1;
+	self->svflags = SVF_NOCLIENT;
+
+	self->think = target_crosslevel_target_think;
+	self->nextthink = level.time + self->delay;
+}
+
+//==========================================================
+
+/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
+When triggered, fires a laser.  You can either set a target
+or a direction.
+*/
+
+void target_laser_think (edict_t *self)
+{
+	edict_t	*ignore;
+	vec3_t	start;
+	vec3_t	end;
+	trace_t	tr;
+	vec3_t	point;
+	vec3_t	last_movedir;
+	int		count;
+
+	if (self->spawnflags & 0x80000000)
+		count = 8;
+	else
+		count = 4;
+
+	if (self->enemy)
+	{
+		VectorCopy (self->movedir, last_movedir);
+		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
+		VectorSubtract (point, self->s.origin, self->movedir);
+		VectorNormalize (self->movedir);
+		if (!VectorCompare(self->movedir, last_movedir))
+			self->spawnflags |= 0x80000000;
+	}
+
+	ignore = self;
+	VectorCopy (self->s.origin, start);
+	VectorMA (start, 2048, self->movedir, end);
+	while(1)
+	{
+		tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+
+		if (!tr.ent)
+			break;
+
+		// hurt it if we can
+		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
+			T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
+
+		// if we hit something that's not a monster or player or is immune to lasers, we're done
+		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		{
+			if (self->spawnflags & 0x80000000)
+			{
+				self->spawnflags &= ~0x80000000;
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (count);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+			}
+			break;
+		}
+
+		ignore = tr.ent;
+		VectorCopy (tr.endpos, start);
+	}
+
+	VectorCopy (tr.endpos, self->s.old_origin);
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void target_laser_on (edict_t *self)
+{
+	if (!self->activator)
+		self->activator = self;
+	self->spawnflags |= 0x80000001;
+	self->svflags &= ~SVF_NOCLIENT;
+	target_laser_think (self);
+}
+
+void target_laser_off (edict_t *self)
+{
+	self->spawnflags &= ~1;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+}
+
+void target_laser_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	if (self->spawnflags & 1)
+		target_laser_off (self);
+	else
+		target_laser_on (self);
+}
+
+void target_laser_start (edict_t *self)
+{
+	edict_t *ent;
+
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
+	self->s.modelindex = 1;			// must be non-zero
+
+	// set the beam diameter
+	if (self->spawnflags & 64)
+		self->s.frame = 16;
+	else
+		self->s.frame = 4;
+
+	// set the color
+	if (self->spawnflags & 2)
+		self->s.skinnum = 0xf2f2f0f0;
+	else if (self->spawnflags & 4)
+		self->s.skinnum = 0xd0d1d2d3;
+	else if (self->spawnflags & 8)
+		self->s.skinnum = 0xf3f3f1f1;
+	else if (self->spawnflags & 16)
+		self->s.skinnum = 0xdcdddedf;
+	else if (self->spawnflags & 32)
+		self->s.skinnum = 0xe0e1e2e3;
+
+	if (!self->enemy)
+	{
+		if (self->target)
+		{
+			ent = G_Find (NULL, FOFS(targetname), self->target);
+			if (!ent)
+				gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
+			self->enemy = ent;
+		}
+		else
+		{
+			G_SetMovedir (self->s.angles, self->movedir);
+		}
+	}
+	self->use = target_laser_use;
+	self->think = target_laser_think;
+
+	if (!self->dmg)
+		self->dmg = 1;
+
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	gi.linkentity (self);
+
+	if (self->spawnflags & 1)
+		target_laser_on (self);
+	else
+		target_laser_off (self);
+}
+
+void SP_target_laser (edict_t *self)
+{
+	// let everything else get spawned before we start firing
+	self->think = target_laser_start;
+	self->nextthink = level.time + 1;
+}
+
+//==========================================================
+
+/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
+speed		How many seconds the ramping will take
+message		two letters; starting lightlevel and ending lightlevel
+*/
+
+void target_lightramp_think (edict_t *self)
+{
+	char	style[2];
+
+	style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
+	style[1] = 0;
+	gi.configstring (CS_LIGHTS+self->enemy->style, style);
+
+	if ((level.time - self->timestamp) < self->speed)
+	{
+		self->nextthink = level.time + FRAMETIME;
+	}
+	else if (self->spawnflags & 1)
+	{
+		char	temp;
+
+		temp = self->movedir[0];
+		self->movedir[0] = self->movedir[1];
+		self->movedir[1] = temp;
+		self->movedir[2] *= -1;
+	}
+}
+
+void target_lightramp_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!self->enemy)
+	{
+		edict_t		*e;
+
+		// check all the targets
+		e = NULL;
+		while (1)
+		{
+			e = G_Find (e, FOFS(targetname), self->target);
+			if (!e)
+				break;
+			if (strcmp(e->classname, "light") != 0)
+			{
+				gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
+				gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
+			}
+			else
+			{
+				self->enemy = e;
+			}
+		}
+
+		if (!self->enemy)
+		{
+			gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
+			G_FreeEdict (self);
+			return;
+		}
+	}
+
+	self->timestamp = level.time;
+	target_lightramp_think (self);
+}
+
+void SP_target_lightramp (edict_t *self)
+{
+	if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
+	{
+		gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->svflags |= SVF_NOCLIENT;
+	self->use = target_lightramp_use;
+	self->think = target_lightramp_think;
+
+	self->movedir[0] = self->message[0] - 'a';
+	self->movedir[1] = self->message[1] - 'a';
+	self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
+}
+
+//==========================================================
+
+/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
+When triggered, this initiates a level-wide earthquake.
+All players and monsters are affected.
+"speed"		severity of the quake (default:200)
+"count"		duration of the quake (default:5)
+*/
+
+void target_earthquake_think (edict_t *self)
+{
+	int		i;
+	edict_t	*e;
+
+	if (self->last_move_time < level.time)
+	{
+		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
+		self->last_move_time = level.time + 0.5;
+	}
+
+	for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->client)
+			continue;
+		if (!e->groundentity)
+			continue;
+
+		e->groundentity = NULL;
+		e->velocity[0] += crandom()* 150;
+		e->velocity[1] += crandom()* 150;
+		e->velocity[2] = self->speed * (100.0 / e->mass);
+	}
+
+	if (level.time < self->timestamp)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void target_earthquake_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->timestamp = level.time + self->count;
+	self->nextthink = level.time + FRAMETIME;
+	self->activator = activator;
+	self->last_move_time = 0;
+}
+
+void SP_target_earthquake (edict_t *self)
+{
+	if (!self->targetname)
+		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->count)
+		self->count = 5;
+
+	if (!self->speed)
+		self->speed = 200;
+
+	self->svflags |= SVF_NOCLIENT;
+	self->think = target_earthquake_think;
+	self->use = target_earthquake_use;
+
+	self->noise_index = gi.soundindex ("world/quake.wav");
+}
--- /dev/null
+++ b/crbot/g_trigger.c
@@ -1,0 +1,585 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void InitTrigger (edict_t *self)
+{
+	if (!VectorCompare (self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+	gi.setmodel (self, self->model);
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+// the wait time has passed, so set back up for another activation
+void multi_wait (edict_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 (edict_t *ent)
+{
+	if (ent->nextthink)
+		return;		// already been triggered
+
+	G_UseTargets (ent, ent->activator);
+
+	if (ent->wait > 0)	
+	{
+		ent->think = multi_wait;
+		ent->nextthink = level.time + ent->wait;
+	}
+	else
+	{	// we can't just remove (self) here, because this is a touch function
+		// called while looping through area links...
+		ent->touch = NULL;
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = G_FreeEdict;
+	}
+}
+
+void Use_Multi (edict_t *ent, edict_t *, edict_t *activator)
+{
+	ent->activator = activator;
+	multi_trigger (ent);
+}
+
+void Touch_Multi (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if(other->client)
+	{
+		if (self->spawnflags & 2)
+			return;
+	}
+	else if (other->svflags & SVF_MONSTER)
+	{
+		if (!(self->spawnflags & 1))
+			return;
+	}
+	else
+		return;
+
+	if (!VectorCompare(self->movedir, vec3_origin))
+	{
+		vec3_t	forward;
+
+		AngleVectors(other->s.angles, forward, NULL, NULL);
+		if (_DotProduct(forward, self->movedir) < 0)
+			return;
+	}
+
+	self->activator = other;
+	multi_trigger (self);
+}
+
+/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
+Variable sized repeatable trigger.  Must be targeted at one or more entities.
+If "delay" is set, the trigger waits some time after activating before firing.
+"wait" : Seconds between triggerings. (.2 default)
+sounds
+1)	secret
+2)	beep beep
+3)	large switch
+4)
+set "message" to text string
+*/
+void trigger_enable (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_TRIGGER;
+	self->use = Use_Multi;
+	gi.linkentity (self);
+}
+
+void SP_trigger_multiple (edict_t *ent)
+{
+	if (ent->sounds == 1)
+		ent->noise_index = gi.soundindex ("misc/secret.wav");
+	else if (ent->sounds == 2)
+		ent->noise_index = gi.soundindex ("misc/talk.wav");
+	else if (ent->sounds == 3)
+		ent->noise_index = gi.soundindex ("misc/trigger1.wav");
+	
+	if (!ent->wait)
+		ent->wait = 0.2;
+	ent->touch = Touch_Multi;
+	ent->movetype = MOVETYPE_NONE;
+	ent->svflags |= SVF_NOCLIENT;
+
+
+	if (ent->spawnflags & 4)
+	{
+		ent->solid = SOLID_NOT;
+		ent->use = trigger_enable;
+	}
+	else
+	{
+		ent->solid = SOLID_TRIGGER;
+		ent->use = Use_Multi;
+	}
+
+	if (!VectorCompare(ent->s.angles, vec3_origin))
+		G_SetMovedir (ent->s.angles, ent->movedir);
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
+Triggers once, then removes itself.
+You must set the key "target" to the name of another object in the level that has a matching "targetname".
+
+If TRIGGERED, this trigger must be triggered before it is live.
+
+sounds
+ 1)	secret
+ 2)	beep beep
+ 3)	large switch
+ 4)
+
+"message"	string to be displayed when triggered
+*/
+
+void SP_trigger_once(edict_t *ent)
+{
+	// make old maps work because I messed up on flag assignments here
+	// triggered was on bit 1 when it should have been on bit 4
+	if (ent->spawnflags & 1)
+	{
+		vec3_t	v;
+
+		VectorMA (ent->mins, 0.5, ent->size, v);
+		ent->spawnflags &= ~1;
+		ent->spawnflags |= 4;
+		gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
+	}
+
+	ent->wait = -1;
+	SP_trigger_multiple (ent);
+}
+
+/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This fixed size trigger cannot be touched, it can only be fired by other events.
+*/
+void trigger_relay_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	G_UseTargets (self, activator);
+}
+
+void SP_trigger_relay (edict_t *self)
+{
+	self->use = trigger_relay_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_key
+
+==============================================================================
+*/
+
+/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
+A relay trigger that only fires it's targets if player has the proper key.
+Use "item" to specify the required key, for example "key_data_cd"
+*/
+void trigger_key_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	int			index;
+
+	if (!self->item)
+		return;
+	if (!activator->client)
+		return;
+
+	index = ITEM_INDEX(self->item);
+	if (!activator->client->pers.inventory[index])
+	{
+		if (level.time < self->touch_debounce_time)
+			return;
+		self->touch_debounce_time = level.time + 5.0;
+		if (!activator->bot_info) gi.centerprintf (activator, "You need the %s", self->item->pickup_name);
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0);
+		return;
+	}
+
+	gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0);
+	if (coop->value)
+	{
+		int		player;
+		edict_t	*ent;
+
+		if (strcmp(self->item->classname, "key_power_cube") == 0)
+		{
+			int	cube;
+
+			for (cube = 0; cube < 8; cube++)
+				if (activator->client->pers.power_cubes & (1 << cube))
+					break;
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				if (ent->client->pers.power_cubes & (1 << cube))
+				{
+					ent->client->pers.inventory[index]--;
+					ent->client->pers.power_cubes &= ~(1 << cube);
+				}
+			}
+		}
+		else
+		{
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				ent->client->pers.inventory[index] = 0;
+			}
+		}
+	}
+	else
+	{
+		activator->client->pers.inventory[index]--;
+	}
+
+	G_UseTargets (self, activator);
+
+	self->use = NULL;
+}
+
+void SP_trigger_key (edict_t *self)
+{
+	if (!st.item)
+	{
+		gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
+		return;
+	}
+	self->item = FindItemByClassname (st.item);
+
+	if (!self->item)
+	{
+		gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin));
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin));
+		return;
+	}
+
+	gi.soundindex ("misc/keytry.wav");
+	gi.soundindex ("misc/keyuse.wav");
+
+	self->use = trigger_key_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_counter
+
+==============================================================================
+*/
+
+/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
+Acts as an intermediary for an action that takes multiple inputs.
+
+If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
+*/
+
+void trigger_counter_use(edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->count == 0)
+		return;
+	
+	self->count--;
+
+	if (self->count)
+	{
+		if (! (self->spawnflags & 1))
+		{
+			if (!activator->bot_info) gi.centerprintf(activator, "%i more to go...", self->count);
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+	
+	if (! (self->spawnflags & 1))
+	{
+		if (!activator->bot_info) gi.centerprintf(activator, "Sequence completed!");
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+	self->activator = activator;
+	multi_trigger (self);
+}
+
+void SP_trigger_counter (edict_t *self)
+{
+	self->wait = -1;
+	if (!self->count)
+		self->count = 2;
+
+	self->use = trigger_counter_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_always
+
+==============================================================================
+*/
+
+/*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 (edict_t *ent)
+{
+	// we must have some delay to make sure our use targets are present
+	if (ent->delay < 0.2)
+		ent->delay = 0.2;
+	G_UseTargets(ent, ent);
+}
+
+
+/*
+==============================================================================
+
+trigger_push
+
+==============================================================================
+*/
+
+#define PUSH_ONCE		1
+
+static int windsound;
+
+void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (strcmp(other->classname, "grenade") == 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+	}
+	else if (other->health > 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+
+		if (other->client)
+		{
+			// don't take falling damage immediately from this
+			VectorCopy (other->velocity, other->client->oldvelocity);
+			if (other->fly_sound_debounce_time < level.time)
+			{
+				other->fly_sound_debounce_time = level.time + 1.5;
+				gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+			}
+			// let it push bots too
+			if (strcmp(other->classname, "bot") == 0)
+				VectorCopy (other->velocity, other->movedir);
+		}
+	}
+	if (self->spawnflags & PUSH_ONCE)
+		G_FreeEdict (self);
+}
+
+
+/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE
+Pushes the player
+"speed"		defaults to 1000
+*/
+void SP_trigger_push (edict_t *self)
+{
+	InitTrigger (self);
+	windsound = gi.soundindex ("misc/windfly.wav");
+	self->touch = trigger_push_touch;
+	if (!self->speed)
+		self->speed = 1000;
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_hurt
+
+==============================================================================
+*/
+
+/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
+Any entity that touches this will be hurt.
+
+It does dmg points of damage each server frame
+
+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 (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+
+void hurt_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	int		dflags;
+
+	if (!other->takedamage)
+		return;
+
+	if (self->timestamp > level.time)
+		return;
+
+	if (self->spawnflags & 16)
+		self->timestamp = level.time + 1;
+	else
+		self->timestamp = level.time + FRAMETIME;
+
+	if (!(self->spawnflags & 4))
+	{
+		if ((level.framenum % 10) == 0)
+			gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
+	}
+
+	if (self->spawnflags & 8)
+		dflags = DAMAGE_NO_PROTECTION;
+	else
+		dflags = 0;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
+}
+
+void SP_trigger_hurt (edict_t *self)
+{
+	InitTrigger (self);
+
+	self->noise_index = gi.soundindex ("world/electro.wav");
+	self->touch = hurt_touch;
+
+	if (!self->dmg)
+		self->dmg = 5;
+
+	if (self->spawnflags & 1)
+		self->solid = SOLID_NOT;
+	else
+		self->solid = SOLID_TRIGGER;
+
+	if (self->spawnflags & 2)
+		self->use = hurt_use;
+
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_gravity
+
+==============================================================================
+*/
+
+/*QUAKED trigger_gravity (.5 .5 .5) ?
+Changes the touching entites gravity to
+the value of "gravity".  1.0 is standard
+gravity for the level.
+*/
+
+void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	other->gravity = self->gravity;
+}
+
+void SP_trigger_gravity (edict_t *self)
+{
+	if (st.gravity == 0)
+	{
+		gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
+		G_FreeEdict  (self);
+		return;
+	}
+
+	InitTrigger (self);
+	self->gravity = atoi(st.gravity);
+	self->touch = trigger_gravity_touch;
+}
+
+
+/*
+==============================================================================
+
+trigger_monsterjump
+
+==============================================================================
+*/
+
+/*QUAKED trigger_monsterjump (.5 .5 .5) ?
+Walking monsters that touch this will jump in the direction of the trigger's angle
+"speed" default to 200, the speed thrown forward
+"height" default to 200, the speed thrown upwards
+*/
+
+void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->flags & (FL_FLY | FL_SWIM) )
+		return;
+	if (other->svflags & SVF_DEADMONSTER)
+		return;
+	if ( !(other->svflags & SVF_MONSTER))
+		return;
+
+// set XY even if not on ground, so the jump will clear lips
+	other->velocity[0] = self->movedir[0] * self->speed;
+	other->velocity[1] = self->movedir[1] * self->speed;
+	
+	if (!other->groundentity)
+		return;
+	
+	other->groundentity = NULL;
+	other->velocity[2] = self->movedir[2];
+}
+
+void SP_trigger_monsterjump (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 200;
+	if (!st.height)
+		st.height = 200;
+	if (self->s.angles[YAW] == 0)
+		self->s.angles[YAW] = 360;
+	InitTrigger (self);
+	self->touch = trigger_monsterjump_touch;
+	self->movedir[2] = st.height;
+}
+
--- /dev/null
+++ b/crbot/g_utils.c
@@ -1,0 +1,541 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
+	result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
+	result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
+}
+
+
+/*
+=============
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+edict_t *G_Find (edict_t *from, int fieldofs, char *match)
+{
+	char	*s;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+
+	for ( ; from < &g_edicts[globals.num_edicts] ; from++)
+	{
+		if (!from->inuse)
+			continue;
+		s = *(char **) ((byte *)from + fieldofs);
+		if (!s)
+			continue;
+		if (!cistrcmp (s, match))
+			return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=================
+findradius
+
+Returns entities that have origins within a spherical area
+
+findradius (origin, radius)
+=================
+*/
+edict_t *findradius (edict_t *from, vec3_t org, float rad)
+{
+	vec3_t	eorg;
+	int		j;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (!from->inuse)
+			continue;
+		if (from->solid == SOLID_NOT)
+			continue;
+		for (j=0 ; j<3 ; j++)
+			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
+		if (VectorLength(eorg) > rad)
+			continue;
+		return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=============
+G_PickTarget
+
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+#define MAXCHOICES	8
+
+edict_t *G_PickTarget (char *targetname)
+{
+	edict_t	*ent = NULL;
+	int		num_choices = 0;
+	edict_t	*choice[MAXCHOICES];
+
+	if (!targetname)
+	{
+		gi.dprintf("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)
+	{
+		gi.dprintf("G_PickTarget: target %s not found\n", targetname);
+		return NULL;
+	}
+
+	return choice[rand() % num_choices];
+}
+
+
+
+void Think_Delay (edict_t *ent)
+{
+	G_UseTargets (ent, ent->activator);
+	G_FreeEdict (ent);
+}
+
+/*
+==============================
+G_UseTargets
+
+the global "activator" should be set to the entity that initiated the firing.
+
+If self.delay is set, a DelayedUse entity will be created that will actually
+do the SUB_UseTargets after that many seconds have passed.
+
+Centerprints any self.message to the activator.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void G_UseTargets (edict_t *ent, edict_t *activator)
+{
+	edict_t		*t;
+
+//
+// check for a delay
+//
+	if (ent->delay)
+	{
+	// create a temp object to fire at a later time
+		t = G_Spawn();
+		t->classname = "DelayedUse";
+		t->nextthink = level.time + ent->delay;
+		t->think = Think_Delay;
+		t->activator = activator;
+		if (!activator)
+			gi.dprintf ("Think_Delay with no activator\n");
+		t->message = ent->message;
+		t->target = ent->target;
+		t->killtarget = ent->killtarget;
+		return;
+	}
+	
+	
+//
+// print the message
+//
+	if ((ent->message) && !(activator->svflags & SVF_MONSTER))
+	{
+		if (!activator->bot_info) gi.centerprintf (activator, "%s", ent->message);
+		if (ent->noise_index)
+			gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
+		else
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+
+//
+// kill killtargets
+//
+	if (ent->killtarget)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
+		{
+			G_FreeEdict (t);
+			if (!ent->inuse)
+			{
+				gi.dprintf("entity was removed while using killtargets\n");
+				return;
+			}
+		}
+	}
+
+//	gi.dprintf("TARGET: activating %s\n", ent->target);
+
+//
+// fire targets
+//
+	if (ent->target)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->target)))
+		{
+			// doors fire area portals in a specific way
+			if (!cistrcmp(t->classname, "func_areaportal") &&
+				(!cistrcmp(ent->classname, "func_door") || !cistrcmp(ent->classname, "func_door_rotating")))
+				continue;
+
+			if (t == ent)
+			{
+				gi.dprintf ("WARNING: Entity used itself.\n");
+			}
+			else
+			{
+				if (t->use)
+					t->use (t, ent, activator);
+			}
+			if (!ent->inuse)
+			{
+				gi.dprintf("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 (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;
+}
+
+
+vec3_t VEC_UP		= {0, -1, 0};
+vec3_t MOVEDIR_UP	= {0, 0, 1};
+vec3_t VEC_DOWN		= {0, -2, 0};
+vec3_t MOVEDIR_DOWN	= {0, 0, -1};
+
+void G_SetMovedir (vec3_t angles, vec3_t movedir)
+{
+	if (VectorCompare (angles, VEC_UP))
+	{
+		VectorCopy (MOVEDIR_UP, movedir);
+	}
+	else if (VectorCompare (angles, VEC_DOWN))
+	{
+		VectorCopy (MOVEDIR_DOWN, movedir);
+	}
+	else
+	{
+		AngleVectors (angles, movedir, NULL, NULL);
+	}
+
+	VectorClear (angles);
+}
+
+
+float vectoyaw (vec3_t vec)
+{
+	float	yaw;
+	
+	if (vec[YAW] == 0 && vec[PITCH] == 0)
+		yaw = 0;
+	else
+	{
+		yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+		if (yaw < 0)
+			yaw += 360;
+	}
+
+	return yaw;
+}
+
+
+void vectoangles (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
+	{
+		yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
+		if (yaw < 0)
+			yaw += 360;
+
+		forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
+		pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
+		if (pitch < 0)
+			pitch += 360;
+	}
+
+	angles[PITCH] = -pitch;
+	angles[YAW] = yaw;
+	angles[ROLL] = 0;
+}
+
+char *G_CopyString (char *in)
+{
+	char	*out;
+	
+	out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
+	strcpy (out, in);
+	return out;
+}
+
+
+void G_InitEdict (edict_t *e)
+{
+	e->inuse = true;
+	e->classname = "noclass";
+	e->gravity = 1.0;
+	e->s.number = e - g_edicts;
+}
+
+/*
+=================
+G_Spawn
+
+Either finds a free edict, or allocates a new one.
+Try to avoid reusing an entity that was recently freed, because it
+can cause the client to think the entity morphed into something else
+instead of being removed and recreated, which can cause interpolated
+angles and bad trails.
+=================
+*/
+edict_t *G_Spawn (void)
+{
+	int			i;
+	edict_t		*e;
+
+	e = &g_edicts[(int)maxclients->value+1];
+	for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
+	{
+		// the first couple seconds of server time can involve a lot of
+		// freeing and allocating, so relax the replacement policy
+		if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
+		{
+			G_InitEdict (e);
+			return e;
+		}
+	}
+	
+	if (i == game.maxentities)
+		gi.error ("ED_Alloc: no free edicts");
+		
+	globals.num_edicts++;
+	G_InitEdict (e);
+	return e;
+}
+
+/*
+=================
+G_FreeEdict
+
+Marks the edict as free
+=================
+*/
+void G_FreeEdict (edict_t *ed)
+{
+	gi.unlinkentity (ed);		// unlink from world
+
+	if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
+	{
+//		gi.dprintf("tried to free special edict\n");
+		return;
+	}
+
+	memset (ed, 0, sizeof(*ed));
+	ed->classname = "freed";
+	ed->freetime = level.time;
+	ed->inuse = false;
+}
+
+
+/*
+============
+G_TouchTriggers
+
+============
+*/
+void	G_TouchTriggers (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	// dead things don't activate triggers!
+	if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
+		return;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_TRIGGERS);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (!hit->touch)
+			continue;
+		hit->touch (hit, ent, NULL, NULL);
+	}
+}
+
+/*
+============
+G_TouchSolids
+
+Call after linking a new trigger in during gameplay
+to force all entities it covers to immediately touch it
+============
+*/
+void	G_TouchSolids (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_SOLID);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (ent->touch)
+			ent->touch (hit, ent, NULL, NULL);
+		if (!ent->inuse)
+			break;
+	}
+}
+
+
+
+
+/*
+==============================================================================
+
+Kill box
+
+==============================================================================
+*/
+
+/*
+=================
+KillBox
+
+Kills all entities that would touch the proposed new positioning
+of ent.  Ent should be unlinked before calling this!
+=================
+*/
+qboolean KillBox (edict_t *ent)
+{
+	trace_t		tr;
+
+	while (1)
+	{
+		tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
+		if (!tr.ent)
+			break;
+
+		// nail it
+		T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+
+		// if we didn't kill it, fail
+		if (tr.ent->solid)
+			return false;
+	}
+
+	return true;		// all clear
+}
--- /dev/null
+++ b/crbot/g_weapon.c
@@ -1,0 +1,902 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+=================
+check_dodge
+
+This is a support routine used when a client is firing
+a non-instant attack weapon.  It checks to see if a
+monster's dodge function should be called.
+=================
+*/
+static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
+{
+	vec3_t	end;
+	vec3_t	v;
+	trace_t	tr;
+	float	eta;
+
+	// easy mode only ducks one quarter the time
+	if (skill->value == 0)
+	{
+		if (qrandom() > 0.25)
+			return;
+	}
+	VectorMA (start, 8192, dir, end);
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
+	{
+		VectorSubtract (tr.endpos, start, v);
+		eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
+		tr.ent->monsterinfo.dodge (tr.ent, self, eta);
+	}
+}
+
+
+/*
+=================
+fire_hit
+
+Used for all impact (hit/punch/slash) attacks
+=================
+*/
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
+{
+	trace_t		tr;
+	vec3_t		forward, right, up;
+	vec3_t		v;
+	vec3_t		point;
+	float		range;
+	vec3_t		dir;
+
+	//see if enemy is in range
+	VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+	range = VectorLength(dir);
+	if (range > aim[0])
+		return false;
+
+	if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
+	{
+		// the hit is straight on so back the range up to the edge of their bbox
+		range -= self->enemy->maxs[0];
+	}
+	else
+	{
+		// this is a side hit so adjust the "right" value out to the edge of their bbox
+		if (aim[1] < 0)
+			aim[1] = self->enemy->mins[0];
+		else
+			aim[1] = self->enemy->maxs[0];
+	}
+
+	VectorMA (self->s.origin, range, dir, point);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
+	if (tr.fraction < 1)
+	{
+		if (!tr.ent->takedamage)
+			return false;
+		// if it will hit any client/monster then hit the one we wanted to hit
+		if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+			tr.ent = self->enemy;
+	}
+
+	AngleVectors(self->s.angles, forward, right, up);
+	VectorMA (self->s.origin, range, forward, point);
+	VectorMA (point, aim[1], right, point);
+	VectorMA (point, aim[2], up, point);
+	VectorSubtract (point, self->enemy->s.origin, dir);
+
+	// do the damage
+	T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
+
+	if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		return false;
+
+	// do our special form of knockback here
+	VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
+	VectorSubtract (v, point, v);
+	VectorNormalize (v);
+	VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
+	if (self->enemy->velocity[2] > 0)
+		self->enemy->groundentity = NULL;
+	return true;
+}
+
+
+/*
+=================
+fire_lead
+
+This is an internal support routine used for bullet/pellet based weapons.
+=================
+*/
+static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
+{
+	trace_t		tr;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	float		r;
+	float		u;
+	vec3_t		water_start;
+	qboolean	water = false;
+	int			content_mask = MASK_SHOT | MASK_WATER;
+
+	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+	if (!(tr.fraction < 1.0))
+	{
+		vectoangles (aimdir, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*hspread;
+		u = crandom()*vspread;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		if (gi.pointcontents (start) & MASK_WATER)
+		{
+			water = true;
+			VectorCopy (start, water_start);
+			content_mask &= ~MASK_WATER;
+		}
+
+		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
+
+		// see if we hit water
+		if (tr.contents & MASK_WATER)
+		{
+			int		color;
+
+			water = true;
+			VectorCopy (tr.endpos, water_start);
+
+			if (!VectorCompare (start, tr.endpos))
+			{
+				if (tr.contents & CONTENTS_WATER)
+				{
+					if (strcmp(tr.surface->name, "*brwater") == 0)
+						color = SPLASH_BROWN_WATER;
+					else
+						color = SPLASH_BLUE_WATER;
+				}
+				else if (tr.contents & CONTENTS_SLIME)
+					color = SPLASH_SLIME;
+				else if (tr.contents & CONTENTS_LAVA)
+					color = SPLASH_LAVA;
+				else
+					color = SPLASH_UNKNOWN;
+
+				if (color != SPLASH_UNKNOWN)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (TE_SPLASH);
+					gi.WriteByte (8);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.WriteByte (color);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+				}
+
+				// change bullet's course when it enters water
+				VectorSubtract (end, start, dir);
+				vectoangles (dir, dir);
+				AngleVectors (dir, forward, right, up);
+				r = crandom()*hspread*2;
+				u = crandom()*vspread*2;
+				VectorMA (water_start, 8192, forward, end);
+				VectorMA (end, r, right, end);
+				VectorMA (end, u, up, end);
+			}
+
+			// re-trace ignoring water this time
+			tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
+		}
+	}
+
+	// send gun puff / flash
+	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+	{
+		if (tr.fraction < 1.0)
+		{
+			if (tr.ent->takedamage)
+			{
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
+			}
+			else
+			{
+				if (strncmp (tr.surface->name, "sky", 3) != 0)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (te_impact);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+
+					if (self->client)
+						PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+				}
+			}
+		}
+	}
+
+	// if went through water, determine where the end and make a bubble trail
+	if (water)
+	{
+		vec3_t	pos;
+
+		VectorSubtract (tr.endpos, water_start, dir);
+		VectorNormalize (dir);
+		VectorMA (tr.endpos, -2, dir, pos);
+		if (gi.pointcontents (pos) & MASK_WATER)
+			VectorCopy (pos, tr.endpos);
+		else
+			tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+
+		VectorAdd (water_start, tr.endpos, pos);
+		VectorScale (pos, 0.5, pos);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BUBBLETRAIL);
+		gi.WritePosition (water_start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (pos, MULTICAST_PVS);
+	}
+}
+
+
+/*
+=================
+fire_bullet
+
+Fires a single round.  Used for machinegun and chaingun.  Would be fine for
+pistols, rifles, etc....
+=================
+*/
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
+{
+	fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_shotgun
+
+Shoots shotgun pellets.  Used by shotgun and super shotgun.
+=================
+*/
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
+{
+	int		i;
+
+	for (i = 0; i < count; i++)
+		fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_blaster
+
+Fires a single blaster bolt.  Used by the blaster and hyper blaster.
+=================
+*/
+void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	int		mod;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		if (self->spawnflags & 1)
+			mod = MOD_HYPERBLASTER;
+		else
+			mod = MOD_BLASTER;
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+	}
+	else
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BLASTER);
+		gi.WritePosition (self->s.origin);
+		if (!plane)
+			gi.WriteDir (vec3_origin);
+		else
+			gi.WriteDir (plane->normal);
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+
+	G_FreeEdict (self);
+}
+
+void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
+{
+	edict_t	*bolt;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn();
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->s.effects |= effect;
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+	bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
+	bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
+	bolt->owner = self;
+	bolt->touch = blaster_touch;
+	bolt->nextthink = level.time + 2;
+	bolt->think = G_FreeEdict;
+	bolt->dmg = damage;
+	bolt->classname = "bolt";
+	if (hyper)
+		bolt->spawnflags = 1;
+	gi.linkentity (bolt);
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+}	
+
+
+/*
+=================
+fire_grenade
+=================
+*/
+static void Grenade_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+	int			mod;
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	//FIXME: if we are onground then raise our Z just a bit since we are a point?
+	if (ent->enemy)
+	{
+		float	points;
+		vec3_t	v;
+		vec3_t	dir;
+
+		VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
+		VectorMA (ent->enemy->s.origin, 0.5, v, v);
+		VectorSubtract (ent->s.origin, v, v);
+		points = ent->dmg - 0.5 * VectorLength (v);
+		VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
+		if (ent->spawnflags & 1)
+			mod = MOD_HANDGRENADE;
+		else
+			mod = MOD_GRENADE;
+		T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+	}
+
+	if (ent->spawnflags & 2)
+		mod = MOD_HELD_GRENADE;
+	else if (ent->spawnflags & 1)
+		mod = MOD_HG_SPLASH;
+	else
+		mod = MOD_G_SPLASH;
+	T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
+
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION_WATER);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	}
+	else
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION);
+	}
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!other->takedamage)
+	{
+		if (ent->spawnflags & 1)
+		{
+			if (qrandom() > 0.5)
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+			else
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+		}
+		else
+		{
+			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+
+	ent->enemy = other;
+	Grenade_Explode (ent);
+}
+
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "grenade";
+
+	gi.linkentity (grenade);
+}
+
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "hgrenade";
+	if (held)
+		grenade->spawnflags = 3;
+	else
+		grenade->spawnflags = 1;
+	grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
+
+	if (timer <= 0.0)
+		Grenade_Explode (grenade);
+	else
+	{
+		gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
+		gi.linkentity (grenade);
+	}
+}
+
+
+/*
+=================
+fire_rocket
+=================
+*/
+void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	vec3_t		origin;
+	int			n;
+
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	// calculate position for the explosion entity
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+	if (other->takedamage)
+	{
+		T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
+	}
+	else
+	{
+		// don't throw any debris in net games
+		if (!deathmatch->value && !coop->value)
+		{
+			if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
+			{
+				n = rand() % 5;
+				while(n--)
+					ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
+			}
+		}
+	}
+
+	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
+
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+		gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
+{
+	edict_t	*rocket;
+
+	rocket = G_Spawn();
+	VectorCopy (start, rocket->s.origin);
+	VectorCopy (dir, rocket->movedir);
+	vectoangles (dir, rocket->s.angles);
+	VectorScale (dir, speed, rocket->velocity);
+	rocket->movetype = MOVETYPE_FLYMISSILE;
+	rocket->clipmask = MASK_SHOT;
+	rocket->solid = SOLID_BBOX;
+	rocket->s.effects |= EF_ROCKET;
+	VectorClear (rocket->mins);
+	VectorClear (rocket->maxs);
+	rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
+	rocket->owner = self;
+	rocket->touch = rocket_touch;
+	rocket->nextthink = level.time + 8000/speed;
+	rocket->think = G_FreeEdict;
+	rocket->dmg = damage;
+	rocket->radius_dmg = radius_damage;
+	rocket->dmg_radius = damage_radius;
+	rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
+	rocket->classname = "rocket";
+
+	if (self->client)
+		check_dodge (self, rocket->s.origin, dir, speed);
+
+	gi.linkentity (rocket);
+}
+
+
+/*
+=================
+fire_rail
+=================
+*/
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
+{
+	vec3_t		from;
+	vec3_t		end;
+	trace_t		tr;
+	edict_t		*ignore;
+	int			mask;
+	qboolean	water;
+
+	VectorMA (start, 8192, aimdir, end);
+	VectorCopy (start, from);
+	ignore = self;
+	water = false;
+	mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
+	while (ignore)
+	{
+		tr = gi.trace (from, NULL, NULL, end, ignore, mask);
+
+		if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
+			water = true;
+		}
+		else
+		{
+			if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+				ignore = tr.ent;
+			else
+				ignore = NULL;
+
+			if ((tr.ent != self) && (tr.ent->takedamage))
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
+		}
+
+		VectorCopy (tr.endpos, from);
+	}
+
+	// send gun puff / flash
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_RAILTRAIL);
+	gi.WritePosition (start);
+	gi.WritePosition (tr.endpos);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+//	gi.multicast (start, MULTICAST_PHS);
+	if (water)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_RAILTRAIL);
+		gi.WritePosition (start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (tr.endpos, MULTICAST_PHS);
+	}
+
+	if (self->client)
+		PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+}
+
+
+/*
+=================
+fire_bfg
+=================
+*/
+void bfg_explode (edict_t *self)
+{
+	edict_t	*ent;
+	float	points;
+	vec3_t	v;
+	float	dist;
+
+	if (self->s.frame == 0)
+	{
+		// the BFG effect
+		ent = NULL;
+		while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
+		{
+			if (!ent->takedamage)
+				continue;
+			if (ent == self->owner)
+				continue;
+			if (!CanDamage (ent, self))
+				continue;
+			if (!CanDamage (ent, self->owner))
+				continue;
+
+			VectorAdd (ent->mins, ent->maxs, v);
+			VectorMA (ent->s.origin, 0.5, v, v);
+			VectorSubtract (self->s.origin, v, v);
+			dist = VectorLength(v);
+			points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
+			if (ent == self->owner)
+				points = points * 0.5;
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_BFG_EXPLOSION);
+			gi.WritePosition (ent->s.origin);
+			gi.multicast (ent->s.origin, MULTICAST_PHS);
+			T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
+		}
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+	self->s.frame++;
+	if (self->s.frame == 5)
+		self->think = G_FreeEdict;
+}
+
+void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	// core explosion - prevents firing it into the wall/floor
+	if (other->takedamage)
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
+	T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
+	self->solid = SOLID_NOT;
+	self->touch = NULL;
+	VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
+	VectorClear (self->velocity);
+	self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
+	self->s.frame = 0;
+	self->s.sound = 0;
+	self->s.effects &= ~EF_ANIM_ALLFAST;
+	self->think = bfg_explode;
+	self->nextthink = level.time + FRAMETIME;
+	self->enemy = other;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BFG_BIGEXPLOSION);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+
+void bfg_think (edict_t *self)
+{
+	edict_t	*ent;
+	edict_t	*ignore;
+	vec3_t	point;
+	vec3_t	dir;
+	vec3_t	start;
+	vec3_t	end;
+	int		dmg;
+	trace_t	tr;
+
+	if (deathmatch->value)
+		dmg = 5;
+	else
+		dmg = 10;
+
+	ent = NULL;
+	while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
+	{
+		if (ent == self)
+			continue;
+
+		if (ent == self->owner)
+			continue;
+
+		if (!ent->takedamage)
+			continue;
+
+		if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
+			continue;
+
+//ZOID
+		//don't target players in CTF
+		if (ctf->value && ent->client &&
+			self->owner->client &&
+			ent->client->resp.ctf_team == self->owner->client->resp.ctf_team)
+			continue;
+//ZOID
+
+		VectorMA (ent->absmin, 0.5, ent->size, point);
+
+		VectorSubtract (point, self->s.origin, dir);
+		VectorNormalize (dir);
+
+		ignore = self;
+		VectorCopy (self->s.origin, start);
+		VectorMA (start, 2048, dir, end);
+		while(1)
+		{
+			tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+
+			if (!tr.ent)
+				break;
+
+			// hurt it if we can
+			if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
+				T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
+
+			// if we hit something that's not a monster or player we're done
+			if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+			{
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (4);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+				break;
+			}
+
+			ignore = tr.ent;
+			VectorCopy (tr.endpos, start);
+		}
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BFG_LASER);
+		gi.WritePosition (self->s.origin);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (self->s.origin, MULTICAST_PHS);
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
+{
+	edict_t	*bfg;
+
+	bfg = G_Spawn();
+	VectorCopy (start, bfg->s.origin);
+	VectorCopy (dir, bfg->movedir);
+	vectoangles (dir, bfg->s.angles);
+	VectorScale (dir, speed, bfg->velocity);
+	bfg->movetype = MOVETYPE_FLYMISSILE;
+	bfg->clipmask = MASK_SHOT;
+	bfg->solid = SOLID_BBOX;
+	bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
+	VectorClear (bfg->mins);
+	VectorClear (bfg->maxs);
+	bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
+	bfg->owner = self;
+	bfg->touch = bfg_touch;
+	bfg->nextthink = level.time + 8000/speed;
+	bfg->think = G_FreeEdict;
+	bfg->radius_dmg = damage;
+	bfg->dmg_radius = damage_radius;
+	bfg->classname = "bfg blast";
+	bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
+
+	bfg->think = bfg_think;
+	bfg->nextthink = level.time + FRAMETIME;
+	bfg->teammaster = bfg;
+	bfg->teamchain = NULL;
+
+	if (self->client)
+		check_dodge (self, bfg->s.origin, dir, speed);
+
+	gi.linkentity (bfg);
+}
--- /dev/null
+++ b/crbot/game.h
@@ -1,0 +1,1743 @@
+// game.h -- game dll information visible to server
+#define	GAME_API_VERSION	3
+// edict->svflags
+
+#define	SVF_NOCLIENT			0x00000001	// don't send entity to clients, even if it has effects
+#define	SVF_DEADMONSTER			0x00000002	// treat as CONTENTS_DEADMONSTER for collision
+#define	SVF_MONSTER				0x00000004	// treat as CONTENTS_MONSTER for collision
+
+// edict->solid values
+
+typedef enum
+{
+	SOLID_NOT,			// no interaction with other objects
+	SOLID_TRIGGER,		// only touch when inside, after moving
+	SOLID_BBOX,			// touch on edge
+	SOLID_BSP			// bsp clip, touch on edge
+} solid_t;
+
+//===============================================================
+
+// link_t is only used for entity area links now
+typedef struct link_s
+{
+	struct link_s	*prev, *next;
+} link_t;
+
+#define	MAX_ENT_CLUSTERS	16
+
+
+typedef struct edict_t edict_t;
+typedef struct gclient_s gclient_t;
+
+//===============================================================
+
+//
+// functions provided by the main engine
+//
+typedef struct
+{
+	// special messages
+	void	(*bprintf) (int printlevel, char *fmt, ...);
+	void	(*dprintf) (char *fmt, ...);
+	void	(*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
+	void	(*centerprintf) (edict_t *ent, char *fmt, ...);
+	void	(*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
+	void	(*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
+
+	// config strings hold all the index strings, the lightstyles,
+	// and misc data like the sky definition and cdtrack.
+	// All of the current configstrings are sent to clients when
+	// they connect, and changes are sent to all connected clients.
+	void	(*configstring) (int num, char *string);
+
+	void	(*error) (char *fmt, ...);
+
+	// the *index functions create configstrings and some internal server state
+	int		(*modelindex) (char *name);
+	int		(*soundindex) (char *name);
+	int		(*imageindex) (char *name);
+
+	void	(*setmodel) (edict_t *ent, char *name);
+
+	// collision detection
+	trace_t	(*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
+	int		(*pointcontents) (vec3_t point);
+	qboolean	(*inPVS) (vec3_t p1, vec3_t p2);
+	qboolean	(*inPHS) (vec3_t p1, vec3_t p2);
+	void		(*SetAreaPortalState) (int portalnum, qboolean open);
+	qboolean	(*AreasConnected) (int area1, int area2);
+
+	// 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.
+	void	(*linkentity) (edict_t *ent);
+	void	(*unlinkentity) (edict_t *ent);		// call before removing an interactive edict
+	int		(*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list,	int maxcount, int areatype);
+	void	(*Pmove) (pmove_t *pmove);		// player movement code common with client prediction
+	// network messaging
+	void	(*multicast) (vec3_t origin, multicast_t to);
+	void	(*unicast) (edict_t *ent, qboolean reliable);
+	void	(*WriteChar) (int c);
+	void	(*WriteByte) (int c);
+	void	(*WriteShort) (int c);
+	void	(*WriteLong) (int c);
+	void	(*WriteFloat) (float f);
+	void	(*WriteString) (char *s);
+	void	(*WritePosition) (vec3_t pos);	// some fractional bits
+	void	(*WriteDir) (vec3_t pos);		// single byte encoded, very coarse
+	void	(*WriteAngle) (float f);
+
+	// managed memory allocation
+	void	*(*TagMalloc) (int size, int tag);
+	void	(*TagFree) (void *block);
+	void	(*FreeTags) (int tag);
+
+	// console variable interaction
+	cvar_t	*(*cvar) (char *var_name, char *value, int flags);
+	cvar_t	*(*cvar_set) (char *var_name, char *value);
+	cvar_t	*(*cvar_forceset) (char *var_name, char *value);
+
+	// ClientCommand and ServerCommand parameter access
+	int		(*argc) (void);
+	char	*(*argv) (int n);
+	char	*(*args) (void);	// concatenation of all argv >= 1
+	// add commands to the server console as if they were typed in
+	// for map changing, etc
+	void	(*AddCommandString) (char *text);
+
+	void	(*DebugGraph) (float value, int color);
+} game_import_t;
+
+//
+// functions exported by the game subsystem
+//
+typedef struct
+{
+	int			apiversion;
+
+	// the init function will only be called when a game starts,
+	// not each time a level is loaded.  Persistant data for clients
+	// and the server can be allocated in init
+	void		(*Init) (void);
+	void		(*Shutdown) (void);
+
+	// each new level entered will cause a call to SpawnEntities
+	void		(*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
+
+	// Read/Write Game is for storing persistant cross level information
+	// about the world state and the clients.
+	// WriteGame is called every time a level is exited.
+	// ReadGame is called on a loadgame.
+	void		(*WriteGame) (char *filename, qboolean autosave);
+	void		(*ReadGame) (char *filename);
+
+	// ReadLevel is called after the default map information has been
+	// loaded with SpawnEntities
+	void		(*WriteLevel) (char *filename);
+	void		(*ReadLevel) (char *filename);
+
+	qboolean	(*ClientConnect) (edict_t *ent, char *userinfo);
+	void		(*ClientBegin) (edict_t *ent);
+	void		(*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
+	void		(*ClientDisconnect) (edict_t *ent);
+	void		(*ClientCommand) (edict_t *ent);
+	void		(*ClientThink) (edict_t *ent, usercmd_t *cmd);
+
+	void		(*RunFrame) (void);
+
+	// ServerCommand will be called when an "sv <command>" command is issued on the
+	// server console.
+	// The game can issue gi.argc() / gi.argv() commands to get the rest
+	// of the parameters
+	void		(*ServerCommand) (void);
+
+	//
+	// global variables shared between game and server
+	//
+
+	// The edict array is allocated in the game dll so it
+	// can vary in size from one game to another.
+	// 
+	// The size will be fixed when ge->Init() is called
+	edict_t		*edicts;
+	int		edict_size;
+	int		num_edicts;		// current number, <= max_edicts
+	int		max_edicts;
+} game_export_t;
+
+game_export_t *GetGameAPI (game_import_t *import);
+
+// g_local.h -- local definitions for game module
+
+// the "gameversion" client command will print this plus compile date
+#define	GAMEVERSION	"baseq2"
+
+// view pitching times
+#define DAMAGE_TIME		0.5
+#define	FALL_TIME		0.3
+
+
+// edict->spawnflags
+// these are set with checkboxes on each entity in the map editor
+#define	SPAWNFLAG_NOT_EASY			0x00000100
+#define	SPAWNFLAG_NOT_MEDIUM		0x00000200
+#define	SPAWNFLAG_NOT_HARD			0x00000400
+#define	SPAWNFLAG_NOT_DEATHMATCH	0x00000800
+#define	SPAWNFLAG_NOT_COOP			0x00001000
+
+// edict->flags
+#define	FL_FLY					0x00000001
+#define	FL_SWIM					0x00000002	// implied immunity to drowining
+#define FL_IMMUNE_LASER			0x00000004
+#define	FL_INWATER				0x00000008
+#define	FL_GODMODE				0x00000010
+#define	FL_NOTARGET				0x00000020
+#define FL_IMMUNE_SLIME			0x00000040
+#define FL_IMMUNE_LAVA			0x00000080
+#define	FL_PARTIALGROUND		0x00000100	// not all corners are valid
+#define	FL_WATERJUMP			0x00000200	// player jumping out of water
+#define	FL_TEAMSLAVE			0x00000400	// not the first on the team
+#define FL_NO_KNOCKBACK			0x00000800
+#define FL_POWER_ARMOR			0x00001000	// power armor (if any) is active
+#define FL_RESPAWN				0x80000000	// used for item respawning
+
+
+#define	FRAMETIME		0.1
+
+// memory tags to allow dynamic memory to be cleaned up
+#define	TAG_GAME	765		// clear when unloading the dll
+#define	TAG_LEVEL	766		// clear when loading a new level
+
+
+#define MELEE_DISTANCE	80
+
+#define BODY_QUEUE_SIZE	   5 //8
+
+typedef enum
+{
+	DAMAGE_NO,
+		DAMAGE_YES,			// will take damage if hit
+	DAMAGE_AIM			// auto targeting recognizes this
+} damage_t;
+
+typedef enum 
+{
+	WEAPON_READY, 
+	WEAPON_ACTIVATING,
+	WEAPON_DROPPING,
+	WEAPON_FIRING
+} weaponstate_t;
+
+typedef enum
+{
+	AMMO_BULLETS,
+		AMMO_SHELLS,
+		AMMO_ROCKETS,
+		AMMO_GRENADES,
+		AMMO_CELLS,
+		AMMO_SLUGS
+} ammo_t;
+
+
+//deadflag
+#define DEAD_NO					0
+#define DEAD_DYING				1
+#define DEAD_DEAD				2
+#define DEAD_RESPAWNABLE		3
+
+//range
+#define RANGE_MELEE				0
+#define RANGE_NEAR				1
+#define RANGE_MID				2
+#define RANGE_FAR				3
+
+//gib types
+#define GIB_ORGANIC				0
+#define GIB_METALLIC			1
+
+//monster ai flags
+#define AI_STAND_GROUND			0x00000001
+#define AI_TEMP_STAND_GROUND	0x00000002
+#define AI_SOUND_TARGET			0x00000004
+#define AI_LOST_SIGHT			0x00000008
+#define AI_PURSUIT_LAST_SEEN	0x00000010
+#define AI_PURSUE_NEXT			0x00000020
+#define AI_PURSUE_TEMP			0x00000040
+#define AI_HOLD_FRAME			0x00000080
+#define AI_GOOD_GUY				0x00000100
+#define AI_BRUTAL				0x00000200
+#define AI_NOSTEP				0x00000400
+#define AI_DUCKED				0x00000800
+#define AI_COMBAT_POINT			0x00001000
+#define AI_MEDIC				0x00002000
+#define AI_RESURRECTING			0x00004000
+
+//monster attack state
+#define AS_STRAIGHT				1
+#define AS_SLIDING				2
+#define	AS_MELEE				3
+#define	AS_MISSILE				4
+
+// armor types
+#define ARMOR_NONE				0
+#define ARMOR_JACKET			1
+#define ARMOR_COMBAT			2
+#define ARMOR_BODY				3
+#define ARMOR_SHARD				4
+
+// power armor types
+#define POWER_ARMOR_NONE		0
+#define POWER_ARMOR_SCREEN		1
+#define POWER_ARMOR_SHIELD		2
+
+// handedness values
+#define RIGHT_HANDED			0
+#define LEFT_HANDED				1
+#define CENTER_HANDED			2
+
+
+// game.serverflags values
+#define SFL_CROSS_TRIGGER_1		0x00000001
+#define SFL_CROSS_TRIGGER_2		0x00000002
+#define SFL_CROSS_TRIGGER_3		0x00000004
+#define SFL_CROSS_TRIGGER_4		0x00000008
+#define SFL_CROSS_TRIGGER_5		0x00000010
+#define SFL_CROSS_TRIGGER_6		0x00000020
+#define SFL_CROSS_TRIGGER_7		0x00000040
+#define SFL_CROSS_TRIGGER_8		0x00000080
+#define SFL_CROSS_TRIGGER_MASK	0x000000ff
+
+
+// noise types for PlayerNoise
+#define PNOISE_SELF				0
+#define PNOISE_WEAPON			1
+#define PNOISE_IMPACT			2
+
+
+// edict->movetype values
+typedef enum
+{
+	MOVETYPE_NONE,			// never moves
+	MOVETYPE_NOCLIP,		// origin and angles change with no interaction
+	MOVETYPE_PUSH,			// no clip to world, push on box contact
+	MOVETYPE_STOP,			// no clip to world, stops on box contact
+
+	MOVETYPE_WALK,			// gravity
+	MOVETYPE_STEP,			// gravity, special edge handling
+	MOVETYPE_FLY,
+		MOVETYPE_TOSS,			// gravity
+	MOVETYPE_FLYMISSILE,	// extra size to monsters
+	MOVETYPE_BOUNCE
+} movetype_t;
+
+
+
+typedef struct
+{
+	int		base_count;
+	int		max_count;
+	float	normal_protection;
+	float	energy_protection;
+	int		armor;
+} gitem_armor_t;
+
+
+// gitem_t->flags
+#define	IT_WEAPON		1		// use makes active weapon
+#define	IT_AMMO			2
+#define IT_ARMOR		4
+#define IT_STAY_COOP	8
+#define IT_KEY			16
+#define IT_POWERUP		32
+//ZOID
+#define IT_TECH			64
+//ZOID
+
+// gitem_t->weapmodel for weapons indicates model index
+#define WEAP_BLASTER			1 
+#define WEAP_SHOTGUN			2 
+#define WEAP_SUPERSHOTGUN		3 
+#define WEAP_MACHINEGUN			4 
+#define WEAP_CHAINGUN			5 
+#define WEAP_GRENADES			6 
+#define WEAP_GRENADELAUNCHER	7 
+#define WEAP_ROCKETLAUNCHER		8 
+#define WEAP_HYPERBLASTER		9 
+#define WEAP_RAILGUN			10
+#define WEAP_BFG				11
+#define WEAP_GRAPPLE			12
+
+typedef struct gitem_s
+{
+	char		*classname;	// spawning name
+	qboolean	(*pickup)(edict_t *ent, edict_t *other);
+	void		(*use)(edict_t *ent, struct gitem_s *item);
+	void		(*drop)(edict_t *ent, struct gitem_s *item);
+	void		(*weaponthink)(edict_t *ent);
+	char		*pickup_sound;
+	char		*world_model;
+	int			world_model_flags;
+	char		*view_model;
+
+	// client side info
+	char		*icon;
+	char		*pickup_name;	// for printing on pickup
+	int			count_width;		// number of digits to display by icon
+	int			quantity;		// for ammo how much, for weapons how much is used per shot
+	char		*ammo;			// for weapons
+	int			flags;			// IT_* flags
+	int			weapmodel;		// weapon model index (for weapons)
+	void		*info;
+	int			tag;
+
+	char		*precaches;		// string of all models, sounds, and images this item will use
+} gitem_t;
+
+
+
+//
+// this structure is left intact through an entire game
+// it should be initialized at dll load time, and read/written to
+// the server.ssv file for savegames
+//
+typedef struct
+{
+	char		helpmessage1[512];
+	char		helpmessage2[512];
+	int			helpchanged;	// flash F1 icon if non 0, play sound
+	// and increment only if 1, 2, or 3
+
+	gclient_t	*clients;		// [maxclients]
+	// can't store spawnpoint in level, because
+	// it would get overwritten by the savegame restore
+	char		spawnpoint[512];	// needed for coop respawns
+	// store latched cvars here that we want to get at often
+	int			maxclients;
+	int			maxentities;
+
+	// cross level triggers
+	int			serverflags;
+
+	// items
+	int			num_items;
+
+	qboolean	autosaved;
+} game_locals_t;
+
+
+//
+// this structure is cleared as each map is entered
+// it is read/written to the level.sav file for savegames
+//
+typedef struct
+{
+	int			framenum;
+	float		time;
+
+	char		level_name[MAX_QPATH];	// the descriptive name (Outer Base, etc)
+	char		mapname[MAX_QPATH];		// the server name (base1, etc)
+	char		nextmap[MAX_QPATH];		// go here when fraglimit is hit
+	char		forcemap[MAX_QPATH];	// go here
+	// intermission state
+	float		intermissiontime;		// time the intermission was started
+	char		*changemap;
+	int			exitintermission;
+	vec3_t		intermission_origin;
+	vec3_t		intermission_angle;
+
+	edict_t		*sight_client;	// changed once each frame for coop games
+	edict_t		*sight_entity;
+	int			sight_entity_framenum;
+	edict_t		*sound_entity;
+	int			sound_entity_framenum;
+	edict_t		*sound2_entity;
+	int			sound2_entity_framenum;
+
+	int			pic_health;
+
+	int			total_secrets;
+	int			found_secrets;
+
+	int			total_goals;
+	int			found_goals;
+
+	int			total_monsters;
+	int			killed_monsters;
+
+	edict_t		*current_entity;	// entity running from G_RunFrame
+	int			body_que;			// dead bodies
+	int			power_cubes;		// ugly necessity for coop
+} level_locals_t;
+
+
+// spawn_temp_t is only used to hold entity field values that
+// can be set from the editor, but aren't actualy present
+// in edict_t during gameplay
+typedef struct
+{
+	// world vars
+	char		*sky;
+	float		skyrotate;
+	vec3_t		skyaxis;
+	char		*nextmap;
+
+	int			lip;
+	int			distance;
+	int			height;
+	char		*noise;
+	float		pausetime;
+	char		*item;
+	char		*gravity;
+
+	float		minyaw;
+	float		maxyaw;
+	float		minpitch;
+	float		maxpitch;
+} spawn_temp_t;
+
+
+typedef struct
+{
+	// fixed data
+	vec3_t		start_origin;
+	vec3_t		start_angles;
+	vec3_t		end_origin;
+	vec3_t		end_angles;
+
+	int			sound_start;
+	int			sound_middle;
+	int			sound_end;
+
+	float		accel;
+	float		speed;
+	float		decel;
+	float		distance;
+
+	float		wait;
+
+	// state data
+	int			state;
+	vec3_t		dir;
+	float		current_speed;
+	float		move_speed;
+	float		next_speed;
+	float		remaining_distance;
+	float		decel_distance;
+	void		(*endfunc)(edict_t *);
+} moveinfo_t;
+
+
+typedef struct
+{
+	void	(*aifunc)(edict_t *self, float dist);
+	float	dist;
+	void	(*thinkfunc)(edict_t *self);
+} mframe_t;
+
+typedef struct
+{
+	int			firstframe;
+	int			lastframe;
+	mframe_t	*frame;
+	void		(*endfunc)(edict_t *self);
+} mmove_t;
+
+typedef struct
+{
+	mmove_t		*currentmove;
+	int			aiflags;
+	int			nextframe;
+	float		scale;
+
+	void		(*stand)(edict_t *self);
+	void		(*idle)(edict_t *self);
+	void		(*search)(edict_t *self);
+	void		(*walk)(edict_t *self);
+	void		(*run)(edict_t *self);
+	void		(*dodge)(edict_t *self, edict_t *other, float eta);
+	void		(*attack)(edict_t *self);
+	void		(*melee)(edict_t *self);
+	void		(*sight)(edict_t *self, edict_t *other);
+	qboolean	(*checkattack)(edict_t *self);
+
+	float		pausetime;
+	float		attack_finished;
+
+	vec3_t		saved_goal;
+	float		search_time;
+	float		trail_time;
+	vec3_t		last_sighting;
+	int			attack_state;
+	int			lefty;
+	float		idle_time;
+	int			linkcount;
+
+	int			power_armor_type;
+	int			power_armor_power;
+} monsterinfo_t;
+
+
+
+extern	game_locals_t	game;
+extern	level_locals_t	level;
+extern	game_import_t	gi;
+extern	game_export_t	globals;
+extern	spawn_temp_t	st;
+
+extern	int	sm_meat_index;
+extern	int	snd_fry;
+
+// means of death
+#define MOD_UNKNOWN			0
+#define MOD_BLASTER			1
+#define MOD_SHOTGUN			2
+#define MOD_SSHOTGUN		3
+#define MOD_MACHINEGUN		4
+#define MOD_CHAINGUN		5
+#define MOD_GRENADE			6
+#define MOD_G_SPLASH		7
+#define MOD_ROCKET			8
+#define MOD_R_SPLASH		9
+#define MOD_HYPERBLASTER	10
+#define MOD_RAILGUN			11
+#define MOD_BFG_LASER		12
+#define MOD_BFG_BLAST		13
+#define MOD_BFG_EFFECT		14
+#define MOD_HANDGRENADE		15
+#define MOD_HG_SPLASH		16
+#define MOD_WATER			17
+#define MOD_SLIME			18
+#define MOD_LAVA			19
+#define MOD_CRUSH			20
+#define MOD_TELEFRAG		21
+#define MOD_FALLING			22
+#define MOD_SUICIDE			23
+#define MOD_HELD_GRENADE	24
+#define MOD_EXPLOSIVE		25
+#define MOD_BARREL			26
+#define MOD_BOMB			27
+#define MOD_EXIT			28
+#define MOD_SPLASH			29
+#define MOD_TARGET_LASER	30
+#define MOD_TRIGGER_HURT	31
+#define MOD_HIT				32
+#define MOD_TARGET_BLASTER	33
+#define MOD_GRAPPLE			34
+#define MOD_FRIENDLY_FIRE	0x8000000
+
+extern	int	meansOfDeath;
+
+
+extern	edict_t			*g_edicts;
+
+#define	FOFS(x) (int)&(((edict_t *)0)->x)
+#define	STOFS(x) (int)&(((spawn_temp_t *)0)->x)
+#define	LLOFS(x) (int)&(((level_locals_t *)0)->x)
+#define	CLOFS(x) (int)&(((gclient_t *)0)->x)
+
+#define qrandom()	((rand () & 0x7fff) / ((float)0x7fff))
+#define crandom()	(2.0 * (qrandom() - 0.5))
+
+extern	cvar_t	*maxentities;
+extern	cvar_t	*deathmatch;
+extern	cvar_t	*coop;
+extern	cvar_t	*dmflags;
+extern	cvar_t	*skill;
+extern	cvar_t	*fraglimit;
+extern	cvar_t	*timelimit;
+//ZOID
+extern	cvar_t	*capturelimit;
+//ZOID
+extern	cvar_t	*password;
+extern	cvar_t	*g_select_empty;
+extern	cvar_t	*dedicated;
+
+extern	cvar_t	*sv_gravity;
+extern	cvar_t	*sv_maxvelocity;
+
+extern	cvar_t	*gun_x, *gun_y, *gun_z;
+extern	cvar_t	*sv_rollspeed;
+extern	cvar_t	*sv_rollangle;
+
+extern	cvar_t	*run_pitch;
+extern	cvar_t	*run_roll;
+extern	cvar_t	*bob_up;
+extern	cvar_t	*bob_pitch;
+extern	cvar_t	*bob_roll;
+
+extern	cvar_t	*sv_cheats;
+extern	cvar_t	*maxclients;
+
+#define WORLD	(&g_edicts[0])
+
+// item spawnflags
+#define ITEM_TRIGGER_SPAWN		0x00000001
+#define ITEM_NO_TOUCH			0x00000002
+// 6 bits reserved for editor flags
+// 8 bits used as power cube id bits for coop games
+#define DROPPED_ITEM			0x00010000
+#define	DROPPED_PLAYER_ITEM		0x00020000
+#define ITEM_TARGETS_USED		0x00040000
+
+//
+// fields are needed for spawning from the entity string
+// and saving / loading games
+//
+#define FFL_SPAWNTEMP		1
+
+typedef enum {
+	F_INT, 
+	F_FLOAT,
+	F_LSTRING,			// string on disk, pointer in memory, TAG_LEVEL
+	F_GSTRING,			// string on disk, pointer in memory, TAG_GAME
+	F_VECTOR,
+	F_ANGLEHACK,
+	F_EDICT,			// 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;
+
+
+extern	field_t fields[];
+extern	gitem_t	itemlist[];
+
+
+//
+// g_cmds.c
+//
+void Cmd_Help_f (edict_t *ent);
+void Cmd_Score_f (edict_t *ent);
+
+//
+// g_items.c
+//
+void PrecacheItem (gitem_t *it);
+void InitItems (void);
+void SetItemNames (void);
+gitem_t	*FindItem (char *pickup_name);
+gitem_t	*FindItemByClassname (char *classname);
+#define	ITEM_INDEX(x) ((x)-itemlist)
+edict_t *Drop_Item (edict_t *ent, gitem_t *item);
+void SetRespawn (edict_t *ent, float delay);
+void ChangeWeapon (edict_t *ent);
+void SpawnItem (edict_t *ent, gitem_t *item);
+void Think_Weapon (edict_t *ent);
+int ArmorIndex (edict_t *ent);
+int PowerArmorType (edict_t *ent);
+gitem_t	*GetItemByIndex (int index);
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count);
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+//
+// g_utils.c
+//
+qboolean	KillBox (edict_t *ent);
+void	G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+edict_t *G_Find (edict_t *from, int fieldofs, char *match);
+edict_t *findradius (edict_t *from, vec3_t org, float rad);
+edict_t *G_PickTarget (char *targetname);
+void	G_UseTargets (edict_t *ent, edict_t *activator);
+void	G_SetMovedir (vec3_t angles, vec3_t movedir);
+
+void	G_InitEdict (edict_t *e);
+edict_t	*G_Spawn (void);
+void	G_FreeEdict (edict_t *e);
+
+void	G_TouchTriggers (edict_t *ent);
+void	G_TouchSolids (edict_t *ent);
+
+char	*G_CopyString (char *in);
+
+float	*tv (float x, float y, float z);
+char	*vtos (vec3_t v);
+
+float vectoyaw (vec3_t vec);
+void vectoangles (vec3_t vec, vec3_t angles);
+
+//
+// g_combat.c
+//
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2);
+qboolean CanDamage (edict_t *targ, edict_t *inflictor);
+qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker);
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod);
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod);
+
+// damage flags
+#define DAMAGE_RADIUS			0x00000001	// damage was indirect
+#define DAMAGE_NO_ARMOR			0x00000002	// armour does not protect from this damage
+#define DAMAGE_ENERGY			0x00000004	// damage is from an energy based weapon
+#define DAMAGE_NO_KNOCKBACK		0x00000008	// do not affect velocity, just view angles
+#define DAMAGE_BULLET			0x00000010  // damage is from a bullet (used for ricochets)
+#define DAMAGE_NO_PROTECTION	0x00000020  // armor, shields, invulnerability, and godmode have no effect
+
+#define DEFAULT_BULLET_HSPREAD	300
+#define DEFAULT_BULLET_VSPREAD	500
+#define DEFAULT_SHOTGUN_HSPREAD	1000
+#define DEFAULT_SHOTGUN_VSPREAD	500
+#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT	12
+#define DEFAULT_SHOTGUN_COUNT	12
+#define DEFAULT_SSHOTGUN_COUNT	20
+
+//
+// g_monster.c
+//
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype);
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype);
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype);
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype);
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype);
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype);
+void M_droptofloor (edict_t *ent);
+void monster_think (edict_t *self);
+void walkmonster_start (edict_t *self);
+void swimmonster_start (edict_t *self);
+void flymonster_start (edict_t *self);
+void AttackFinished (edict_t *self, float time);
+void monster_death_use (edict_t *self);
+void M_CatagorizePosition (edict_t *ent);
+qboolean M_CheckAttack (edict_t *self);
+void M_FlyCheck (edict_t *self);
+void M_CheckGround (edict_t *ent);
+
+//
+// g_misc.c
+//
+void ThrowHead (edict_t *self, char *gibname, int damage, int type);
+void ThrowClientHead (edict_t *self, int damage);
+void ThrowGib (edict_t *self, char *gibname, int damage, int type);
+void BecomeExplosion1(edict_t *self);
+
+//
+// g_ai.c
+//
+void AI_SetSightClient (void);
+
+void ai_stand (edict_t *self, float dist);
+void ai_move (edict_t *self, float dist);
+void ai_walk (edict_t *self, float dist);
+void ai_turn (edict_t *self, float dist);
+void ai_run (edict_t *self, float dist);
+void ai_charge (edict_t *self, float dist);
+int range (edict_t *self, edict_t *other);
+
+void FoundTarget (edict_t *self);
+qboolean infront (edict_t *self, edict_t *other);
+qboolean visible (edict_t *self, edict_t *other);
+qboolean FacingIdeal(edict_t *self);
+
+//
+// g_weapon.c
+//
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin);
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick);
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod);
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod);
+void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper);
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius);
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held);
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage);
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);
+
+//
+// g_ptrail.c
+//
+void PlayerTrail_Init (void);
+void PlayerTrail_Add (vec3_t spot);
+void PlayerTrail_New (vec3_t spot);
+edict_t *PlayerTrail_PickFirst (edict_t *self);
+edict_t *PlayerTrail_PickNext (edict_t *self);
+edict_t	*PlayerTrail_LastSpot (void);
+
+
+//
+// g_client.c
+//
+void respawn (edict_t *ent);
+void BeginIntermission (edict_t *targ);
+void PutClientInServer (edict_t *ent);
+void InitClientPersistant (gclient_t *client);
+void InitClientResp (gclient_t *client);
+void InitBodyQue (void);
+void ClientBeginServerFrame (edict_t *ent);
+
+//
+// g_player.c
+//
+void player_pain (edict_t *self, edict_t *other, float kick, int damage);
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+//
+// g_svcmds.c
+//
+void	ServerCommand (void);
+
+//
+// p_view.c
+//
+void ClientEndServerFrame (edict_t *ent);
+
+//
+// p_hud.c
+//
+void MoveClientToIntermission (edict_t *client);
+void G_SetStats (edict_t *ent);
+void ValidateSelectedItem (edict_t *ent);
+void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer);
+
+//
+// g_pweapon.c
+//
+void PlayerNoise(edict_t *who, vec3_t where, int type);
+void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent));
+
+//
+// m_move.c
+//
+qboolean M_CheckBottom (edict_t *ent);
+qboolean M_walkmove (edict_t *ent, float yaw, float dist);
+void M_MoveToGoal (edict_t *ent, float dist);
+void M_ChangeYaw (edict_t *ent);
+
+//
+// g_phys.c
+//
+void G_RunEntity (edict_t *ent);
+
+//
+// g_main.c
+//
+void SaveClientData (void);
+void FetchClientEntData (edict_t *ent);
+
+
+//============================================================================
+
+// client_t->anim_priority
+#define	ANIM_BASIC		0		// stand / run
+#define	ANIM_WAVE		1
+#define	ANIM_JUMP		2
+#define	ANIM_PAIN		3
+#define	ANIM_ATTACK		4
+#define	ANIM_DEATH		5
+
+
+// client data that stays across multiple level loads
+typedef struct
+{
+	char		userinfo[MAX_INFO_STRING];
+	char		netname[16];
+	int			hand;
+
+	qboolean	connected;			// a loadgame will leave valid entities that
+	// just don't have a connection yet
+
+	// values saved and restored from edicts when changing levels
+	int			health;
+	int			max_health;
+	qboolean	powerArmorActive;
+
+	int			selected_item;
+	int			inventory[MAX_ITEMS];
+
+	// ammo capacities
+	int			max_bullets;
+	int			max_shells;
+	int			max_rockets;
+	int			max_grenades;
+	int			max_cells;
+	int			max_slugs;
+
+	gitem_t		*weapon;
+	gitem_t		*lastweapon;
+
+	int			power_cubes;	// used for tracking the cubes in coop games
+	int			score;			// for calculating total unit score in coop games
+	//===
+	int         team_no;
+
+} client_persistant_t;
+
+// client data that stays across deathmatch respawns
+typedef struct
+{
+	client_persistant_t	coop_respawn;	// what to set client->pers to on a respawn
+	int			enterframe;			// level.framenum the client entered the game
+	int			score;				// frags, etc
+	//ZOID
+	int			ctf_team;			// CTF team
+	int			ctf_state;
+	float		ctf_lasthurtcarrier;
+	float		ctf_lastreturnedflag;
+	float		ctf_flagsince;
+	float		ctf_lastfraggedcarrier;
+	qboolean	id_state;
+	//ZOID
+	vec3_t		cmd_angles;			// angles sent over in the last command
+	int			game_helpchanged;
+	int			helpchanged;
+} client_respawn_t;
+
+enum {
+	PMENU_ALIGN_LEFT,
+	PMENU_ALIGN_CENTER,
+	PMENU_ALIGN_RIGHT
+};
+
+typedef struct pmenuhnd_s {
+	struct pmenu_s *entries;
+	int cur;
+	int num;
+} pmenuhnd_t;
+
+typedef struct pmenu_s {
+	char *text;
+	int align;
+	void *arg;
+	void (*SelectFunc)(edict_t *ent, struct pmenu_s *entry);
+} pmenu_t;
+
+void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num);
+void PMenu_Close(edict_t *ent);
+void PMenu_Update(edict_t *ent);
+void PMenu_Next(edict_t *ent);
+void PMenu_Prev(edict_t *ent);
+void PMenu_Select(edict_t *ent);
+
+
+
+
+
+// this structure is cleared on each PutClientInServer(),
+// except for 'client->pers'
+struct gclient_s
+{
+	// known to server
+	player_state_t	ps;				// communicated by server to clients
+	int				ping;
+
+	// private to game
+	client_persistant_t	pers;
+	client_respawn_t	resp;
+	pmove_state_t		old_pmove;	// for detecting out-of-pmove changes
+	qboolean	showscores;			// set layout stat
+	//ZOID
+	qboolean	inmenu;				// in menu
+	pmenuhnd_t	*menu;				// current menu
+	//ZOID
+	qboolean	showinventory;		// set layout stat
+	qboolean	showhelp;
+	qboolean	showhelpicon;
+
+	int			ammo_index;
+
+	int			buttons;
+	int			oldbuttons;
+	int			latched_buttons;
+
+	qboolean	weapon_thunk;
+
+	gitem_t		*newweapon;
+
+	// sum up damage over an entire frame, so
+	// shotgun blasts give a single big kick
+	int			damage_armor;		// damage absorbed by armor
+	int			damage_parmor;		// damage absorbed by power armor
+	int			damage_blood;		// damage taken out of health
+	int			damage_knockback;	// impact damage
+	vec3_t		damage_from;		// origin for vector calculation
+	float		killer_yaw;			// when dead, look at killer
+	weaponstate_t	weaponstate;
+	vec3_t		kick_angles;	// weapon kicks
+	vec3_t		kick_origin;
+	float		v_dmg_roll, v_dmg_pitch, v_dmg_time;	// damage kicks
+	float		fall_time, fall_value;		// for view drop on fall
+	float		damage_alpha;
+	float		bonus_alpha;
+	vec3_t		damage_blend;
+	vec3_t		v_angle;			// aiming direction
+	float		bobtime;			// so off-ground doesn't change it
+	vec3_t		oldviewangles;
+	vec3_t		oldvelocity;
+
+	float		next_drown_time;
+	int			old_waterlevel;
+	int			breather_sound;
+
+	int			machinegun_shots;	// for weapon raising
+	// animation vars
+	int			anim_end;
+	int			anim_priority;
+	qboolean	anim_duck;
+	qboolean	anim_run;
+
+	// powerup timers
+	float		quad_framenum;
+	float		invincible_framenum;
+	float		breather_framenum;
+	float		enviro_framenum;
+
+	qboolean	grenade_blew_up;
+	float		grenade_time;
+	int			silencer_shots;
+	int			weapon_sound;
+
+	float		pickup_msg_time;
+
+	float		respawn_time;		// can respawn when time > this
+	//ZOID
+	void		*ctf_grapple;		// entity of grapple
+	int			ctf_grapplestate;		// true if pulling
+	float		ctf_grapplereleasetime;	// time of grapple release
+	float		ctf_regentime;		// regen tech
+	float		ctf_techsndtime;
+	float		ctf_lasttechmsg;
+	edict_t		*chase_target;
+	qboolean	update_chase;
+	//ZOID
+
+	//=== cr_menu data
+	int         menu_no, menu_item_no;
+	qboolean    inuse;
+};
+
+#define MAX_NODE_LINKS  6
+
+#define NF_ELEVATOR  0x0001
+#define NF_TELEPORT  0x0002
+#define NF_DOOR      0x0004
+#define NF_BUTTON    0x0008
+#define NF_LADDER    0x0010
+
+typedef struct path_node_s path_node_t;
+struct path_node_s {
+
+	vec3_t       position;
+
+	path_node_t *next;
+
+	path_node_t* link_to[MAX_NODE_LINKS];
+	path_node_t* link_from[MAX_NODE_LINKS];
+	float        link_dist[MAX_NODE_LINKS];
+
+	edict_t     *item;
+	float        time;
+
+	int          flags;
+
+	// used in finding routes
+	float        route_dist;
+};
+
+
+typedef struct bot_info_pers_s bot_info_pers_t;
+struct bot_info_pers_s {
+	int      skill;
+	int      team_no, adapt_count;
+	char     skin[64], model[64], name[16];
+	float    speed, rot_speed, attack_range, engage_range;
+	qboolean b_inuse, b_adapting;
+
+	int      playernum;
+};
+
+#define MAX_UNREACHABLES 12
+#define MAX_PATH_NODES   256
+
+#define ASSN_NONE    0
+#define ASSN_HELP    1
+#define ASSN_GUARD   3
+#define ASSN_AMBUSH  4
+#define ASSN_GROUP   5
+#define ASSN_PATROL  6
+
+typedef struct bot_info_s bot_info_t;
+struct bot_info_s {
+	float        time_last_stuck, time_next_enemy, time_next_pickup,
+	time_last_strafe_switch, time_next_shot, time_stop_shoting, 
+	time_chase, time_next_chase_update, time_next_assignment_check,
+	time_next_roam_dir_change, time_stuck_check, time_next_crouch,
+	time_last_message, time_next_fight_message, time_next_jump,
+	time_next_weapon_change, time_next_rocket_avoid, 
+	time_weapon_spin_up, time_weapon_spin_down, time_next_special_assignment,
+	time_next_solid, time_next_callforhelp, time_next_salute,
+	time_last_move_target;
+	float        strafe_dir;
+	vec3_t       move_target, old_origin, shoot_last_target, last_move_target;
+	edict_t     *pickup_target;
+	float        pickup_target_score;
+	path_node_t *last_node, *next_node, *target_node;
+	void       (*old_think)(edict_t *self);
+	qboolean     b_crouch, b_on_slope, b_airborn, b_shot_this_frame, 
+		b_on_platform, b_on_ladder;
+	vec3_t       ladder_dir;
+	int          stuck_count, move_block_count;
+
+	edict_t     *unreachable[MAX_UNREACHABLES];
+	float        time_unreachable[MAX_UNREACHABLES];
+
+	path_node_t* path[MAX_PATH_NODES];
+	int          path_nodes;
+
+	int          bot_assignment;
+	vec3_t       bot_anchor;
+	edict_t*     team_leader;
+
+	void        (*postthink) (edict_t *self);
+};
+
+
+struct edict_t
+{
+	entity_state_t	s;
+	struct gclient_s	*client;	// NULL if not a player
+	// the server expects the first part
+	// of gclient_s to be a player_state_t
+	// but the rest of it is opaque
+
+	qboolean	inuse;
+	int			linkcount;
+
+	// FIXME: move these fields to a server private sv_entity_t
+	link_t		area;				// linked to a division node or leaf
+
+	int			num_clusters;		// if -1, use headnode instead
+	int			clusternums[MAX_ENT_CLUSTERS];
+	int			headnode;			// unused if num_clusters != -1
+	int			areanum, areanum2;
+
+	//================================
+
+	int			svflags;
+	vec3_t		mins, maxs;
+	vec3_t		absmin, absmax, size;
+	solid_t		solid;
+	int			clipmask;
+	edict_t		*owner;
+
+
+	// DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER
+	// EXPECTS THE FIELDS IN THAT ORDER!
+
+	//================================
+	int			movetype;
+	int			flags;
+
+	char		*model;
+	float		freetime;			// sv.time when the object was freed
+
+	//
+	// only used locally in game, not by server
+	//
+	char		*message;
+	char		*classname;
+	int			spawnflags;
+
+	float		timestamp;
+
+	float		angle;			// set in qe3, -1 = up, -2 = down
+	char		*target;
+	char		*targetname;
+	char		*killtarget;
+	char		*team;
+	char		*pathtarget;
+	char		*deathtarget;
+	char		*combattarget;
+	edict_t		*target_ent;
+
+	float		speed, accel, decel;
+	vec3_t		movedir;
+	vec3_t		pos1, pos2;
+
+	vec3_t		velocity;
+	vec3_t		avelocity;
+	int			mass;
+	float		air_finished;
+	float		gravity;		// per entity gravity multiplier (1.0 is normal)
+	// use for lowgrav artifact, flares
+
+	edict_t		*goalentity;
+	edict_t		*movetarget;
+	float		yaw_speed;
+	float		ideal_yaw;
+
+	float		nextthink;
+	void		(*prethink) (edict_t *ent);
+	void		(*think)(edict_t *self);
+	void		(*blocked)(edict_t *self, edict_t *other);	//move to moveinfo?
+	void		(*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+	void		(*use)(edict_t *self, edict_t *other, edict_t *activator);
+	void		(*pain)(edict_t *self, edict_t *other, float kick, int damage);
+	void		(*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+	float		touch_debounce_time;		// are all these legit?  do we need more/less of them?
+	float		pain_debounce_time;
+	float		damage_debounce_time;
+	float		fly_sound_debounce_time;	//move to clientinfo
+	float		last_move_time;
+
+	int			health;
+	int			max_health;
+	int			gib_health;
+	int			deadflag;
+	qboolean	show_hostile;
+
+	float		powerarmor_time;
+
+	char		*map;			// target_changelevel
+	int			viewheight;		// height above origin where eyesight is determined
+	int			takedamage;
+	int			dmg;
+	int			radius_dmg;
+	float		dmg_radius;
+	int			sounds;			//make this a spawntemp var?
+	int			count;
+
+	edict_t		*chain;
+	edict_t		*enemy;
+	edict_t		*oldenemy;
+	edict_t		*activator;
+	edict_t		*groundentity;
+	int			groundentity_linkcount;
+	edict_t		*teamchain;
+	edict_t		*teammaster;
+
+	edict_t		*mynoise;		// can go in client only
+	edict_t		*mynoise2;
+
+	int			noise_index;
+	int			noise_index2;
+	float		volume;
+	float		attenuation;
+
+	// timing variables
+	float		wait;
+	float		delay;			// before firing targets
+	float		random;
+
+	float		teleport_time;
+
+	int			watertype;
+	int			waterlevel;
+
+	vec3_t		move_origin;
+	vec3_t		move_angles;
+
+	// move this to clientinfo?
+	int			light_level;
+
+	int			style;			// also used as areaportal number
+	gitem_t		*item;			// for bonus items
+	//== CRbot specific
+	bot_info_t*       bot_info;
+	bot_info_pers_t*  bot_pers;
+	path_node_t*      prev_node;
+	//==
+
+	// common data blocks
+	moveinfo_t		moveinfo;
+	monsterinfo_t	monsterinfo;
+};
+
+#define CTF_STRING_VERSION	"1.02"
+
+#define STAT_CTF_TEAM1_PIC			17
+#define STAT_CTF_TEAM1_CAPS			18
+#define STAT_CTF_TEAM2_PIC			19
+#define STAT_CTF_TEAM2_CAPS			20
+#define STAT_CTF_FLAG_PIC			21
+#define STAT_CTF_JOINED_TEAM1_PIC	22
+#define STAT_CTF_JOINED_TEAM2_PIC	23
+#define STAT_CTF_TEAM1_HEADER		24
+#define STAT_CTF_TEAM2_HEADER		25
+#define STAT_CTF_TECH				26
+#define STAT_CTF_ID_VIEW			27
+
+typedef enum {
+	CTF_NOTEAM,
+	CTF_TEAM1,
+	CTF_TEAM2
+} ctfteam_t;
+
+typedef enum {
+	CTF_STATE_START,
+	CTF_STATE_PLAYING
+} ctfstate_t;
+
+typedef enum {
+	CTF_GRAPPLE_STATE_FLY,
+	CTF_GRAPPLE_STATE_PULL,
+	CTF_GRAPPLE_STATE_HANG
+} ctfgrapplestate_t;
+
+extern cvar_t *ctf;
+
+#define CTF_TEAM1_SKIN "ctf_r"
+#define CTF_TEAM2_SKIN "ctf_b"
+
+#define DF_CTF_FORCEJOIN	131072	
+#define DF_ARMOR_PROTECT	262144
+#define DF_CTF_NO_TECH      524288
+
+#define CTF_CAPTURE_BONUS		15	// what you get for capture
+#define CTF_TEAM_BONUS			10	// what your team gets for capture
+#define CTF_RECOVERY_BONUS		1	// what you get for recovery
+#define CTF_FLAG_BONUS			0	// what you get for picking up enemy flag
+#define CTF_FRAG_CARRIER_BONUS	2	// what you get for fragging enemy flag carrier
+#define CTF_FLAG_RETURN_TIME	40	// seconds until auto return
+
+#define CTF_CARRIER_DANGER_PROTECT_BONUS	2	// bonus for fraggin someone who has recently hurt your flag carrier
+#define CTF_CARRIER_PROTECT_BONUS			1	// bonus for fraggin someone while either you or your target are near your flag carrier
+#define CTF_FLAG_DEFENSE_BONUS				1	// bonus for fraggin someone while either you or your target are near your flag
+#define CTF_RETURN_FLAG_ASSIST_BONUS		1	// awarded for returning a flag that causes a capture to happen almost immediately
+#define CTF_FRAG_CARRIER_ASSIST_BONUS		2	// award for fragging a flag carrier if a capture happens almost immediately
+
+#define CTF_TARGET_PROTECT_RADIUS			400	// the radius around an object being defended where a target will be worth extra frags
+#define CTF_ATTACKER_PROTECT_RADIUS			400	// the radius around an object being defended where an attacker will get extra frags when making kills
+
+#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT	8
+#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT		10
+#define CTF_RETURN_FLAG_ASSIST_TIMEOUT		10
+
+#define CTF_AUTO_FLAG_RETURN_TIMEOUT		30	// number of seconds before dropped flag auto-returns
+
+#define CTF_TECH_TIMEOUT					60  // seconds before techs spawn again
+
+#define CTF_GRAPPLE_SPEED					650 // speed of grapple in flight
+#define CTF_GRAPPLE_PULL_SPEED				650	// speed player is pulled at
+
+void CTFInit(void);
+
+void SP_info_player_team1(edict_t *self);
+void SP_info_player_team2(edict_t *self);
+
+char *CTFTeamName(int team);
+char *CTFOtherTeamName(int team);
+void CTFAssignSkin(edict_t *ent, char *s);
+void CTFAssignTeam(gclient_t *who);
+edict_t *SelectCTFSpawnPoint (edict_t *ent);
+qboolean CTFPickup_Flag(edict_t *ent, edict_t *other);
+void CTFDrop_Flag(edict_t *ent, gitem_t *item);
+void CTFEffects(edict_t *player);
+void CTFCalcScores(void);
+void SetCTFStats(edict_t *ent);
+void CTFDeadDropFlag(edict_t *self);
+void CTFScoreboardMessage (edict_t *ent, edict_t *killer);
+void CTFTeam_f (edict_t *ent);
+void CTFID_f (edict_t *ent);
+void CTFSay_Team(edict_t *who, char *msg);
+void CTFFlagSetup (edict_t *ent);
+void CTFResetFlag(int ctf_team);
+void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker);
+
+// GRAPPLE
+void CTFWeapon_Grapple (edict_t *ent);
+void CTFPlayerResetGrapple(edict_t *ent);
+void CTFGrapplePull(edict_t *self);
+void CTFResetGrapple(edict_t *self);
+
+//TECH
+gitem_t *CTFWhat_Tech(edict_t *ent);
+qboolean CTFPickup_Tech (edict_t *ent, edict_t *other);
+void CTFDrop_Tech(edict_t *ent, gitem_t *item);
+void CTFDeadDropTech(edict_t *ent);
+void CTFSetupTechSpawn(void);
+int CTFApplyResistance(edict_t *ent, int dmg);
+int CTFApplyStrength(edict_t *ent, int dmg);
+qboolean CTFApplyStrengthSound(edict_t *ent);
+qboolean CTFApplyHaste(edict_t *ent);
+void CTFApplyHasteSound(edict_t *ent);
+void CTFApplyRegeneration(edict_t *ent);
+qboolean CTFHasRegeneration(edict_t *ent);
+void CTFRespawnTech(edict_t *ent);
+
+void CTFOpenJoinMenu(edict_t *ent);
+qboolean CTFStartClient(edict_t *ent);
+
+qboolean CTFCheckRules(void);
+
+void SP_misc_ctf_banner (edict_t *ent);
+void SP_misc_ctf_small_banner (edict_t *ent);
+
+extern char *ctf_statusbar;
+
+void UpdateChaseCam(edict_t *ent);
+void ChaseNext(edict_t *ent);
+void ChasePrev(edict_t *ent);
+
+void SP_trigger_teleport (edict_t *ent);
+void SP_info_teleport_destination (edict_t *ent);
+
+void CTFJoinTeam(edict_t *ent, int desired_team);
+
+// G:\quake2\baseq2\models/player_x/frames
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_run1            	40
+#define FRAME_run2            	41
+#define FRAME_run3            	42
+#define FRAME_run4            	43
+#define FRAME_run5            	44
+#define FRAME_run6            	45
+#define FRAME_attack1         	46
+#define FRAME_attack2         	47
+#define FRAME_attack3         	48
+#define FRAME_attack4         	49
+#define FRAME_attack5         	50
+#define FRAME_attack6         	51
+#define FRAME_attack7         	52
+#define FRAME_attack8         	53
+#define FRAME_pain101         	54
+#define FRAME_pain102         	55
+#define FRAME_pain103         	56
+#define FRAME_pain104         	57
+#define FRAME_pain201         	58
+#define FRAME_pain202         	59
+#define FRAME_pain203         	60
+#define FRAME_pain204         	61
+#define FRAME_pain301         	62
+#define FRAME_pain302         	63
+#define FRAME_pain303         	64
+#define FRAME_pain304         	65
+#define FRAME_jump1           	66
+#define FRAME_jump2           	67
+#define FRAME_jump3           	68
+#define FRAME_jump4           	69
+#define FRAME_jump5           	70
+#define FRAME_jump6           	71
+#define FRAME_flip01          	72
+#define FRAME_flip02          	73
+#define FRAME_flip03          	74
+#define FRAME_flip04          	75
+#define FRAME_flip05          	76
+#define FRAME_flip06          	77
+#define FRAME_flip07          	78
+#define FRAME_flip08          	79
+#define FRAME_flip09          	80
+#define FRAME_flip10          	81
+#define FRAME_flip11          	82
+#define FRAME_flip12          	83
+#define FRAME_salute01        	84
+#define FRAME_salute02        	85
+#define FRAME_salute03        	86
+#define FRAME_salute04        	87
+#define FRAME_salute05        	88
+#define FRAME_salute06        	89
+#define FRAME_salute07        	90
+#define FRAME_salute08        	91
+#define FRAME_salute09        	92
+#define FRAME_salute10        	93
+#define FRAME_salute11        	94
+#define FRAME_taunt01         	95
+#define FRAME_taunt02         	96
+#define FRAME_taunt03         	97
+#define FRAME_taunt04         	98
+#define FRAME_taunt05         	99
+#define FRAME_taunt06         	100
+#define FRAME_taunt07         	101
+#define FRAME_taunt08         	102
+#define FRAME_taunt09         	103
+#define FRAME_taunt10         	104
+#define FRAME_taunt11         	105
+#define FRAME_taunt12         	106
+#define FRAME_taunt13         	107
+#define FRAME_taunt14         	108
+#define FRAME_taunt15         	109
+#define FRAME_taunt16         	110
+#define FRAME_taunt17         	111
+#define FRAME_wave01          	112
+#define FRAME_wave02          	113
+#define FRAME_wave03          	114
+#define FRAME_wave04          	115
+#define FRAME_wave05          	116
+#define FRAME_wave06          	117
+#define FRAME_wave07          	118
+#define FRAME_wave08          	119
+#define FRAME_wave09          	120
+#define FRAME_wave10          	121
+#define FRAME_wave11          	122
+#define FRAME_point01         	123
+#define FRAME_point02         	124
+#define FRAME_point03         	125
+#define FRAME_point04         	126
+#define FRAME_point05         	127
+#define FRAME_point06         	128
+#define FRAME_point07         	129
+#define FRAME_point08         	130
+#define FRAME_point09         	131
+#define FRAME_point10         	132
+#define FRAME_point11         	133
+#define FRAME_point12         	134
+#define FRAME_crstnd01        	135
+#define FRAME_crstnd02        	136
+#define FRAME_crstnd03        	137
+#define FRAME_crstnd04        	138
+#define FRAME_crstnd05        	139
+#define FRAME_crstnd06        	140
+#define FRAME_crstnd07        	141
+#define FRAME_crstnd08        	142
+#define FRAME_crstnd09        	143
+#define FRAME_crstnd10        	144
+#define FRAME_crstnd11        	145
+#define FRAME_crstnd12        	146
+#define FRAME_crstnd13        	147
+#define FRAME_crstnd14        	148
+#define FRAME_crstnd15        	149
+#define FRAME_crstnd16        	150
+#define FRAME_crstnd17        	151
+#define FRAME_crstnd18        	152
+#define FRAME_crstnd19        	153
+#define FRAME_crwalk1         	154
+#define FRAME_crwalk2         	155
+#define FRAME_crwalk3         	156
+#define FRAME_crwalk4         	157
+#define FRAME_crwalk5         	158
+#define FRAME_crwalk6         	159
+#define FRAME_crattak1        	160
+#define FRAME_crattak2        	161
+#define FRAME_crattak3        	162
+#define FRAME_crattak4        	163
+#define FRAME_crattak5        	164
+#define FRAME_crattak6        	165
+#define FRAME_crattak7        	166
+#define FRAME_crattak8        	167
+#define FRAME_crattak9        	168
+#define FRAME_crpain1         	169
+#define FRAME_crpain2         	170
+#define FRAME_crpain3         	171
+#define FRAME_crpain4         	172
+#define FRAME_crdeath1        	173
+#define FRAME_crdeath2        	174
+#define FRAME_crdeath3        	175
+#define FRAME_crdeath4        	176
+#define FRAME_crdeath5        	177
+#define FRAME_death101        	178
+#define FRAME_death102        	179
+#define FRAME_death103        	180
+#define FRAME_death104        	181
+#define FRAME_death105        	182
+#define FRAME_death106        	183
+#define FRAME_death201        	184
+#define FRAME_death202        	185
+#define FRAME_death203        	186
+#define FRAME_death204        	187
+#define FRAME_death205        	188
+#define FRAME_death206        	189
+#define FRAME_death301        	190
+#define FRAME_death302        	191
+#define FRAME_death303        	192
+#define FRAME_death304        	193
+#define FRAME_death305        	194
+#define FRAME_death306        	195
+#define FRAME_death307        	196
+#define FRAME_death308        	197
+
+#define MODEL_SCALE		1.000000
+
+#define CRBOT_VER  "v1.14"
+#define CRBOT_INFO "CRBot " CRBOT_VER ", coded by Mike Malakhov\nwith CTF support\nhttp://www.pobox.com/~ww/bot.html \n\nUse inventory keys to access bot's menu\n\nTo add bots type 'sv addbots 0 <amount>'\nin the console window"
+
+void SP_crbot( char* name, int skill_level, char* skin, int team, char* model );
+void cr_kill_bot( char* bot_name );
+void cr_team_help( edict_t* self );
+void cr_team_group( edict_t* self );
+void cr_team_guard( edict_t* self, char* name );
+void cr_team_patrol( edict_t* self, char* name );
+void cr_team_free( edict_t* self, char* name );
+
+void cr_menu_init(void);
+void cr_menu_draw( edict_t *self );
+void cr_menu_select( edict_t *self, int ds );
+void cr_menu_down( edict_t *self );
+void cr_menu_up( edict_t *self );
+
+void cr_init(void);
+void cr_init_globals(void);
+void cr_client_connect(edict_t *ent);
+void cr_exit_level(void);
+void cr_routes_save( edict_t *self );
+
+// void cr_scoreboard_message( char* string, edict_t *self );
+void cr_show_stats( edict_t *self );
+void cr_show_team_stats( edict_t *self );
+
+extern cvar_t *bot_skill;
+extern cvar_t *bot_skin;
+extern cvar_t *bot_model;
+extern cvar_t *bot_team;
+extern cvar_t *bot_chat;
+extern cvar_t *bot_taunt;
+extern cvar_t *obituary_msgs;
+
+extern cvar_t *bot_menu;
+
+extern cvar_t *game_path;
+extern cvar_t *base_path;
+
+extern cvar_t *map_cycle;
+extern cvar_t *no_tech;
+extern cvar_t *no_hook;
+
+extern cvar_t *bot_debug;
+
+void cr_get_full_pathname( char* fullname, char* name );
+
+void ShowGun( edict_t *ent );
+
+qboolean cr_map_cycle(void);
+extern char next_map_name[64];
--- /dev/null
+++ b/crbot/m_flash.c
@@ -1,0 +1,490 @@
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+*/
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+// this file is included in both the game dll and quake2,
+// the game needs it to source shot locations, the client
+// needs it to position muzzle flashes
+vec3_t monster_flash_offset [] =
+{
+// flash 0 is not used
+	0.0, 0.0, 0.0,
+
+// MZ2_TANK_BLASTER_1				1
+	20.7, -18.5, 28.7,
+// MZ2_TANK_BLASTER_2				2
+	16.6, -21.5, 30.1,
+// MZ2_TANK_BLASTER_3				3
+	11.8, -23.9, 32.1,
+// MZ2_TANK_MACHINEGUN_1			4
+	22.9, -0.7, 25.3,
+// MZ2_TANK_MACHINEGUN_2			5
+	22.2, 6.2, 22.3,
+// MZ2_TANK_MACHINEGUN_3			6
+	19.4, 13.1, 18.6,
+// MZ2_TANK_MACHINEGUN_4			7
+	19.4, 18.8, 18.6,
+// MZ2_TANK_MACHINEGUN_5			8
+	17.9, 25.0, 18.6,
+// MZ2_TANK_MACHINEGUN_6			9
+	14.1, 30.5, 20.6,
+// MZ2_TANK_MACHINEGUN_7			10
+	9.3, 35.3, 22.1,
+// MZ2_TANK_MACHINEGUN_8			11
+	4.7, 38.4, 22.1,
+// MZ2_TANK_MACHINEGUN_9			12
+	-1.1, 40.4, 24.1,
+// MZ2_TANK_MACHINEGUN_10			13
+	-6.5, 41.2, 24.1,
+// MZ2_TANK_MACHINEGUN_11			14
+	3.2, 40.1, 24.7,
+// MZ2_TANK_MACHINEGUN_12			15
+	11.7, 36.7, 26.0,
+// MZ2_TANK_MACHINEGUN_13			16
+	18.9, 31.3, 26.0,
+// MZ2_TANK_MACHINEGUN_14			17
+	24.4, 24.4, 26.4,
+// MZ2_TANK_MACHINEGUN_15			18
+	27.1, 17.1, 27.2,
+// MZ2_TANK_MACHINEGUN_16			19
+	28.5, 9.1, 28.0,
+// MZ2_TANK_MACHINEGUN_17			20
+	27.1, 2.2, 28.0,
+// MZ2_TANK_MACHINEGUN_18			21
+	24.9, -2.8, 28.0,
+// MZ2_TANK_MACHINEGUN_19			22
+	21.6, -7.0, 26.4,
+// MZ2_TANK_ROCKET_1				23
+	6.2, 29.1, 49.1,
+// MZ2_TANK_ROCKET_2				24
+	6.9, 23.8, 49.1,
+// MZ2_TANK_ROCKET_3				25
+	8.3, 17.8, 49.5,
+
+// MZ2_INFANTRY_MACHINEGUN_1		26
+	26.6, 7.1, 13.1,
+// MZ2_INFANTRY_MACHINEGUN_2		27
+	18.2, 7.5, 15.4,
+// MZ2_INFANTRY_MACHINEGUN_3		28
+	17.2, 10.3, 17.9,
+// MZ2_INFANTRY_MACHINEGUN_4		29
+	17.0, 12.8, 20.1,
+// MZ2_INFANTRY_MACHINEGUN_5		30
+	15.1, 14.1, 21.8,
+// MZ2_INFANTRY_MACHINEGUN_6		31
+	11.8, 17.2, 23.1,
+// MZ2_INFANTRY_MACHINEGUN_7		32
+	11.4, 20.2, 21.0,
+// MZ2_INFANTRY_MACHINEGUN_8		33
+	9.0, 23.0, 18.9,
+// MZ2_INFANTRY_MACHINEGUN_9		34
+	13.9, 18.6, 17.7,
+// MZ2_INFANTRY_MACHINEGUN_10		35
+	15.4, 15.6, 15.8,
+// MZ2_INFANTRY_MACHINEGUN_11		36
+	10.2, 15.2, 25.1,
+// MZ2_INFANTRY_MACHINEGUN_12		37
+	-1.9, 15.1, 28.2,
+// MZ2_INFANTRY_MACHINEGUN_13		38
+	-12.4, 13.0, 20.2,
+
+// MZ2_SOLDIER_BLASTER_1			39
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_2			40
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_1			41
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_2			42
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_1			43
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_2			44
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+
+// MZ2_GUNNER_MACHINEGUN_1			45
+	30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_2			46
+	29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_3			47
+	28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_4			48
+	28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_5			49
+	26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_6			50
+	26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_7			51
+	26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_8			52
+	29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15,
+// MZ2_GUNNER_GRENADE_1				53
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_2				54
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_3				55
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_4				56
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+
+// MZ2_CHICK_ROCKET_1				57
+//	-24.8, -9.0, 39.0,
+	24.8, -9.0, 39.0,			// PGM - this was incorrect in Q2
+
+// MZ2_FLYER_BLASTER_1				58
+	12.1, 13.4, -14.5,
+// MZ2_FLYER_BLASTER_2				59
+	12.1, -7.4, -14.5,
+
+// MZ2_MEDIC_BLASTER_1				60
+	12.1, 5.4, 16.5,
+
+// MZ2_GLADIATOR_RAILGUN_1			61
+	30.0, 18.0, 28.0,
+
+// MZ2_HOVER_BLASTER_1				62
+	32.5, -0.8, 10.0,
+
+// MZ2_ACTOR_MACHINEGUN_1			63
+	18.4, 7.4, 9.6,
+
+// MZ2_SUPERTANK_MACHINEGUN_1		64
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_2		65
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_3		66
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_4		67
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_5		68
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_6		69
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_ROCKET_1			70
+	16.0, -22.5, 91.2,
+// MZ2_SUPERTANK_ROCKET_2			71
+	16.0, -33.4, 86.7,
+// MZ2_SUPERTANK_ROCKET_3			72
+	16.0, -42.8, 83.3,
+
+// --- Start Xian Stuff ---
+// MZ2_BOSS2_MACHINEGUN_L1			73
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L2			74
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L3			75
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L4			76
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L5			77
+	32,	-40,	70,
+// --- End Xian Stuff
+
+// MZ2_BOSS2_ROCKET_1				78
+	22.0, 16.0, 10.0,
+// MZ2_BOSS2_ROCKET_2				79
+	22.0, 8.0, 10.0,
+// MZ2_BOSS2_ROCKET_3				80
+	22.0, -8.0, 10.0,
+// MZ2_BOSS2_ROCKET_4				81
+	22.0, -16.0, 10.0,
+
+// MZ2_FLOAT_BLASTER_1				82
+	32.5, -0.8, 10,
+
+// MZ2_SOLDIER_BLASTER_3			83
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_3			84
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_3			85
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_4			86
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_4			87
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_4			88
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_5			89
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_5			90
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_5			91
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_6			92
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_6			93
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_6			94
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_BLASTER_7			95
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_7			96
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_7			97
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_BLASTER_8			98
+//	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+	31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_8			99
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_8			100
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+
+// --- Xian shit below ---
+// MZ2_MAKRON_BFG					101
+	17,		-19.5,	62.9,
+// MZ2_MAKRON_BLASTER_1				102
+	-3.6,	-24.1,	59.5,
+// MZ2_MAKRON_BLASTER_2				103
+	-1.6,	-19.3,	59.5,
+// MZ2_MAKRON_BLASTER_3				104
+	-0.1,	-14.4,	59.5,		
+// MZ2_MAKRON_BLASTER_4				105
+	2.0,	-7.6,	59.5,	
+// MZ2_MAKRON_BLASTER_5				106
+	3.4,	1.3,	59.5,
+// MZ2_MAKRON_BLASTER_6				107
+	3.7,	11.1,	59.5,	
+// MZ2_MAKRON_BLASTER_7				108
+	-0.3,	22.3,	59.5,
+// MZ2_MAKRON_BLASTER_8				109
+	-6,		33,		59.5,
+// MZ2_MAKRON_BLASTER_9				110
+	-9.3,	36.4,	59.5,
+// MZ2_MAKRON_BLASTER_10			111
+	-7,		35,		59.5,
+// MZ2_MAKRON_BLASTER_11			112
+	-2.1,	29,		59.5,
+// MZ2_MAKRON_BLASTER_12			113
+	3.9,	17.3,	59.5,
+// MZ2_MAKRON_BLASTER_13			114
+	6.1,	5.8,	59.5,
+// MZ2_MAKRON_BLASTER_14			115
+	5.9,	-4.4,	59.5,
+// MZ2_MAKRON_BLASTER_15			116
+	4.2,	-14.1,	59.5,		
+// MZ2_MAKRON_BLASTER_16			117
+	2.4,	-18.8,	59.5,
+// MZ2_MAKRON_BLASTER_17			118
+	-1.8,	-25.5,	59.5,
+// MZ2_MAKRON_RAILGUN_1				119
+	-17.3,	7.8,	72.4,
+
+// MZ2_JORG_MACHINEGUN_L1			120
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L2			121
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L3			122
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L4			123
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L5			124
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L6			125
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_R1			126
+	78.5,	46.7,  96,			
+// MZ2_JORG_MACHINEGUN_R2			127
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R3			128
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R4			129
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R5			130
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R6			131
+	78.5,	46.7,	96,			
+// MZ2_JORG_BFG_1					132
+	6.3,	-9,		111.2,
+
+// MZ2_BOSS2_MACHINEGUN_R1			73
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R2			74
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R3			75
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R4			76
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R5			77
+	32,	40,	70,
+
+// --- End Xian Shit ---
+
+// ROGUE
+// note that the above really ends at 137
+// carrier machineguns
+// MZ2_CARRIER_MACHINEGUN_L1
+	56,	-32, 32,
+// MZ2_CARRIER_MACHINEGUN_R1
+	56,	32, 32,
+// MZ2_CARRIER_GRENADE
+	42,	24, 50,
+// MZ2_TURRET_MACHINEGUN			141
+	16, 0, 0,
+// MZ2_TURRET_ROCKET				142
+	16, 0, 0,
+// MZ2_TURRET_BLASTER				143
+	16, 0, 0,
+// MZ2_STALKER_BLASTER				144
+	24, 0, 6,
+// MZ2_DAEDALUS_BLASTER				145
+	32.5, -0.8, 10.0,
+// MZ2_MEDIC_BLASTER_2				146
+	12.1, 5.4, 16.5,
+// MZ2_CARRIER_RAILGUN				147
+	32, 0, 6, 
+// MZ2_WIDOW_DISRUPTOR				148
+	57.72, 14.50, 88.81,
+// MZ2_WIDOW_BLASTER				149
+	56,	32, 32,
+// MZ2_WIDOW_RAIL					150
+	62, -20, 84, 
+// MZ2_WIDOW_PLASMABEAM				151		// PMM - not used!
+	32, 0, 6, 
+// MZ2_CARRIER_MACHINEGUN_L2		152
+	61,	-32, 12,
+// MZ2_CARRIER_MACHINEGUN_R2		153
+	61,	32, 12,
+// MZ2_WIDOW_RAIL_LEFT				154
+	17, -62, 91, 
+// MZ2_WIDOW_RAIL_RIGHT				155
+	68, 12, 86, 
+// MZ2_WIDOW_BLASTER_SWEEP1			156			pmm - the sweeps need to be in sequential order
+	47.5, 56, 89,
+// MZ2_WIDOW_BLASTER_SWEEP2			157
+	54, 52, 91,
+// MZ2_WIDOW_BLASTER_SWEEP3			158
+	58, 40, 91,
+// MZ2_WIDOW_BLASTER_SWEEP4			159
+	68, 30, 88,
+// MZ2_WIDOW_BLASTER_SWEEP5			160
+	74, 20, 88,
+// MZ2_WIDOW_BLASTER_SWEEP6			161
+	73, 11, 87,
+// MZ2_WIDOW_BLASTER_SWEEP7			162
+	73, 3, 87,
+// MZ2_WIDOW_BLASTER_SWEEP8			163
+	70, -12, 87,
+// MZ2_WIDOW_BLASTER_SWEEP9			164
+	67, -20, 90,
+// MZ2_WIDOW_BLASTER_100			165
+	-20, 76, 90,
+// MZ2_WIDOW_BLASTER_90				166
+	-8, 74, 90,
+// MZ2_WIDOW_BLASTER_80				167
+	0, 72, 90,
+// MZ2_WIDOW_BLASTER_70				168		d06
+	10, 71, 89,
+// MZ2_WIDOW_BLASTER_60				169		d07
+	23, 70, 87,
+// MZ2_WIDOW_BLASTER_50				170		d08
+	32, 64, 85,
+// MZ2_WIDOW_BLASTER_40				171
+	40, 58, 84,
+// MZ2_WIDOW_BLASTER_30				172		d10
+	48, 50, 83,
+// MZ2_WIDOW_BLASTER_20				173
+	54, 42, 82,
+// MZ2_WIDOW_BLASTER_10				174		d12
+	56, 34, 82,
+// MZ2_WIDOW_BLASTER_0				175
+	58, 26, 82,
+// MZ2_WIDOW_BLASTER_10L			176		d14
+	60, 16, 82,
+// MZ2_WIDOW_BLASTER_20L			177
+	59, 6, 81,
+// MZ2_WIDOW_BLASTER_30L			178		d16
+	58, -2, 80,
+// MZ2_WIDOW_BLASTER_40L			179
+	57, -10, 79,
+// MZ2_WIDOW_BLASTER_50L			180		d18
+	54, -18, 78,
+// MZ2_WIDOW_BLASTER_60L			181
+	42, -32, 80,
+// MZ2_WIDOW_BLASTER_70L			182		d20
+	36, -40, 78,
+// MZ2_WIDOW_RUN_1					183
+	68.4, 10.88, 82.08,
+// MZ2_WIDOW_RUN_2					184
+	68.51, 8.64, 85.14,
+// MZ2_WIDOW_RUN_3					185
+	68.66, 6.38, 88.78,
+// MZ2_WIDOW_RUN_4					186
+	68.73, 5.1, 84.47,
+// MZ2_WIDOW_RUN_5					187
+	68.82, 4.79, 80.52,
+// MZ2_WIDOW_RUN_6					188
+	68.77, 6.11, 85.37,
+// MZ2_WIDOW_RUN_7					189
+	68.67, 7.99, 90.24,
+// MZ2_WIDOW_RUN_8					190
+	68.55, 9.54, 87.36,
+// MZ2_CARRIER_ROCKET_1				191
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_2				192
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_3				193
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_4				194
+	0, 0, -5,
+// MZ2_WIDOW2_BEAMER_1				195
+//	72.13, -17.63, 93.77,
+	69.00, -17.63, 93.77,
+// MZ2_WIDOW2_BEAMER_2				196
+//	71.46, -17.08, 89.82,
+	69.00, -17.08, 89.82,
+// MZ2_WIDOW2_BEAMER_3				197
+//	71.47, -18.40, 90.70,
+	69.00, -18.40, 90.70,
+// MZ2_WIDOW2_BEAMER_4				198
+//	71.96, -18.34, 94.32,
+	69.00, -18.34, 94.32,
+// MZ2_WIDOW2_BEAMER_5				199
+//	72.25, -18.30, 97.98,
+	69.00, -18.30, 97.98,
+// MZ2_WIDOW2_BEAM_SWEEP_1			200
+	45.04, -59.02, 92.24,
+// MZ2_WIDOW2_BEAM_SWEEP_2			201
+	50.68, -54.70, 91.96,
+// MZ2_WIDOW2_BEAM_SWEEP_3			202
+	56.57, -47.72, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_4			203
+	61.75, -38.75, 91.38,
+// MZ2_WIDOW2_BEAM_SWEEP_5			204
+	65.55, -28.76, 91.24,
+// MZ2_WIDOW2_BEAM_SWEEP_6			205
+	67.79, -18.90, 91.22,
+// MZ2_WIDOW2_BEAM_SWEEP_7			206
+	68.60, -9.52, 91.23,
+// MZ2_WIDOW2_BEAM_SWEEP_8			207
+	68.08, 0.18, 91.32,
+// MZ2_WIDOW2_BEAM_SWEEP_9			208
+	66.14, 9.79, 91.44,
+// MZ2_WIDOW2_BEAM_SWEEP_10			209
+	62.77, 18.91, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_11			210
+	58.29, 27.11, 92.00,
+
+// end of table
+	0.0, 0.0, 0.0
+};
--- /dev/null
+++ b/crbot/m_move.c
@@ -1,0 +1,539 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define	STEPSIZE	18
+
+/*
+=============
+M_CheckBottom
+
+Returns false if any part of the bottom of the entity is off an edge that
+is not a staircase.
+
+=============
+*/
+int c_yes, c_no;
+
+qboolean M_CheckBottom (edict_t *ent)
+{
+	vec3_t	mins, maxs, start, stop;
+	trace_t	trace;
+	int		x, y;
+	float	mid, bottom;
+	
+	VectorAdd (ent->s.origin, ent->mins, mins);
+	VectorAdd (ent->s.origin, ent->maxs, maxs);
+
+// if all of the points under the corners are solid world, don't bother
+// with the tougher checks
+// the corners must be within 16 of the midpoint
+	start[2] = mins[2] - 1;
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = x ? maxs[0] : mins[0];
+			start[1] = y ? maxs[1] : mins[1];
+			if (gi.pointcontents (start) != CONTENTS_SOLID)
+				goto realcheck;
+		}
+
+	c_yes++;
+	return true;		// we got out easy
+
+realcheck:
+	c_no++;
+//
+// check it for real...
+//
+	start[2] = mins[2];
+	
+// the midpoint must be within 16 of the bottom
+	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+	stop[2] = start[2] - 2*STEPSIZE;
+	trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1.0)
+		return false;
+	mid = bottom = trace.endpos[2];
+	
+// the corners must be within 16 of the midpoint	
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = stop[0] = x ? maxs[0] : mins[0];
+			start[1] = stop[1] = y ? maxs[1] : mins[1];
+			
+			trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+			
+			if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+				bottom = trace.endpos[2];
+			if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+				return false;
+		}
+
+	c_yes++;
+	return true;
+}
+
+
+/*
+=============
+SV_movestep
+
+Called by monster program code.
+The move will be adjusted for slopes and stairs, but if the move isn't
+possible, no move is done, false is returned, and
+pr_global_struct->trace_normal is set to the normal of the blocking wall
+=============
+*/
+//FIXME since we need to test end position contents here, can we avoid doing
+//it again later in catagorize position?
+qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
+{
+	float		dz;
+	vec3_t		oldorg, neworg, end;
+	trace_t		trace;
+	int			i;
+	float		stepsize;
+	vec3_t		test;
+	int			contents;
+
+// try the move	
+	VectorCopy (ent->s.origin, oldorg);
+	VectorAdd (ent->s.origin, move, neworg);
+
+// flying monsters don't step up
+	if ( ent->flags & (FL_SWIM | FL_FLY) )
+	{
+	// try one move with vertical motion, then one without
+		for (i=0 ; i<2 ; i++)
+		{
+			VectorAdd (ent->s.origin, move, neworg);
+			if (i == 0 && ent->enemy)
+			{
+				if (!ent->goalentity)
+					ent->goalentity = ent->enemy;
+				dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+				if (ent->goalentity->client)
+				{
+					if (dz > 40)
+						neworg[2] -= 8;
+					if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+						if (dz < 30)
+							neworg[2] += 8;
+				}
+				else
+				{
+					if (dz > 8)
+						neworg[2] -= 8;
+					else if (dz > 0)
+						neworg[2] -= dz;
+					else if (dz < -8)
+						neworg[2] += 8;
+					else
+						neworg[2] += dz;
+				}
+			}
+			trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
+	
+			// fly monsters don't enter water voluntarily
+			if (ent->flags & FL_FLY)
+			{
+				if (!ent->waterlevel)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (contents & MASK_WATER)
+						return false;
+				}
+			}
+
+			// swim monsters don't exit water voluntarily
+			if (ent->flags & FL_SWIM)
+			{
+				if (ent->waterlevel < 2)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (!(contents & MASK_WATER))
+						return false;
+				}
+			}
+
+			if (trace.fraction == 1)
+			{
+				VectorCopy (trace.endpos, ent->s.origin);
+				if (relink)
+				{
+					gi.linkentity (ent);
+					G_TouchTriggers (ent);
+				}
+				return true;
+			}
+			
+			if (!ent->enemy)
+				break;
+		}
+		
+		return false;
+	}
+
+// push down from a step height above the wished position
+	if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+		stepsize = STEPSIZE;
+	else
+		stepsize = 1;
+
+	neworg[2] += stepsize;
+	VectorCopy (neworg, end);
+	end[2] -= stepsize*2;
+
+	trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.allsolid)
+		return false;
+
+	if (trace.startsolid)
+	{
+		neworg[2] -= stepsize;
+		trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+		if (trace.allsolid || trace.startsolid)
+			return false;
+	}
+
+
+	// don't go in to water
+	if (ent->waterlevel == 0)
+	{
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		test[2] = trace.endpos[2] + ent->mins[2] + 1;	
+		contents = gi.pointcontents(test);
+
+		if (contents & MASK_WATER)
+			return false;
+	}
+
+	if (trace.fraction == 1)
+	{
+	// if monster had the ground pulled out, go ahead and fall
+		if ( ent->flags & FL_PARTIALGROUND )
+		{
+			VectorAdd (ent->s.origin, move, ent->s.origin);
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			ent->groundentity = NULL;
+			return true;
+		}
+	
+		return false;		// walked off an edge
+	}
+
+// check point traces down for dangling corners
+	VectorCopy (trace.endpos, ent->s.origin);
+	
+	if (!M_CheckBottom (ent))
+	{
+		if ( ent->flags & FL_PARTIALGROUND )
+		{	// entity had floor mostly pulled out from underneath it
+			// and is trying to correct
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			return true;
+		}
+		VectorCopy (oldorg, ent->s.origin);
+		return false;
+	}
+
+	if ( ent->flags & FL_PARTIALGROUND )
+	{
+		ent->flags &= ~FL_PARTIALGROUND;
+	}
+	ent->groundentity = trace.ent;
+	ent->groundentity_linkcount = trace.ent->linkcount;
+
+// the move is ok
+	if (relink)
+	{
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+	}
+	return true;
+}
+
+
+//============================================================================
+
+/*
+===============
+M_ChangeYaw
+
+===============
+*/
+void M_ChangeYaw (edict_t *ent)
+{
+	float	ideal;
+	float	current;
+	float	move;
+	float	speed;
+	
+	current = anglemod(ent->s.angles[YAW]);
+	ideal = ent->ideal_yaw;
+
+	if (current == ideal)
+		return;
+
+	move = ideal - current;
+	speed = ent->yaw_speed;
+	if (ideal > current)
+	{
+		if (move >= 180)
+			move = move - 360;
+	}
+	else
+	{
+		if (move <= -180)
+			move = move + 360;
+	}
+	if (move > 0)
+	{
+		if (move > speed)
+			move = speed;
+	}
+	else
+	{
+		if (move < -speed)
+			move = -speed;
+	}
+	
+	ent->s.angles[YAW] = anglemod (current + move);
+}
+
+
+/*
+======================
+SV_StepDirection
+
+Turns to the movement direction, and walks the current distance if
+facing it.
+
+======================
+*/
+qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
+{
+	vec3_t		move, oldorigin;
+	float		delta;
+	
+	ent->ideal_yaw = yaw;
+	M_ChangeYaw (ent);
+	
+	yaw = yaw*M_PI*2 / 360;
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	VectorCopy (ent->s.origin, oldorigin);
+	if (SV_movestep (ent, move, false))
+	{
+		delta = ent->s.angles[YAW] - ent->ideal_yaw;
+		if (delta > 45 && delta < 315)
+		{		// not turned far enough, so don't take the step
+			VectorCopy (oldorigin, ent->s.origin);
+		}
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+		return true;
+	}
+	gi.linkentity (ent);
+	G_TouchTriggers (ent);
+	return false;
+}
+
+/*
+======================
+SV_FixCheckBottom
+
+======================
+*/
+void SV_FixCheckBottom (edict_t *ent)
+{
+	ent->flags |= FL_PARTIALGROUND;
+}
+
+
+
+/*
+================
+SV_NewChaseDir
+
+================
+*/
+#define	DI_NODIR	-1
+void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
+{
+	float	deltax,deltay;
+	float	d[3];
+	float	tdir, olddir, turnaround;
+
+	//FIXME: how did we get here with no enemy
+	if (!enemy)
+		return;
+
+	olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
+	turnaround = anglemod(olddir - 180);
+
+	deltax = enemy->s.origin[0] - actor->s.origin[0];
+	deltay = enemy->s.origin[1] - actor->s.origin[1];
+	if (deltax>10)
+		d[1]= 0;
+	else if (deltax<-10)
+		d[1]= 180;
+	else
+		d[1]= DI_NODIR;
+	if (deltay<-10)
+		d[2]= 270;
+	else if (deltay>10)
+		d[2]= 90;
+	else
+		d[2]= DI_NODIR;
+
+// try direct route
+	if (d[1] != DI_NODIR && d[2] != DI_NODIR)
+	{
+		if (d[1] == 0)
+			tdir = d[2] == 90 ? 45 : 315;
+		else
+			tdir = d[2] == 90 ? 135 : 215;
+			
+		if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
+			return;
+	}
+
+// try other directions
+	if ( ((rand()&3) & 1) ||  abs(deltay)>abs(deltax))
+	{
+		tdir=d[1];
+		d[1]=d[2];
+		d[2]=tdir;
+	}
+
+	if (d[1]!=DI_NODIR && d[1]!=turnaround 
+	&& SV_StepDirection(actor, d[1], dist))
+			return;
+
+	if (d[2]!=DI_NODIR && d[2]!=turnaround
+	&& SV_StepDirection(actor, d[2], dist))
+			return;
+
+/* there is no direct path to the player, so pick another direction */
+
+	if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
+			return;
+
+	if (rand()&1) 	/*randomly determine direction of search*/
+	{
+		for (tdir=0 ; tdir<=315 ; tdir += 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+	else
+	{
+		for (tdir=315 ; tdir >=0 ; tdir -= 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+
+	if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
+			return;
+
+	actor->ideal_yaw = olddir;		// can't move
+
+// if a bridge was pulled out from underneath a monster, it may not have
+// a valid standing position at all
+
+	if (!M_CheckBottom (actor))
+		SV_FixCheckBottom (actor);
+}
+
+/*
+======================
+SV_CloseEnough
+
+======================
+*/
+qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
+{
+	int		i;
+	
+	for (i=0 ; i<3 ; i++)
+	{
+		if (goal->absmin[i] > ent->absmax[i] + dist)
+			return false;
+		if (goal->absmax[i] < ent->absmin[i] - dist)
+			return false;
+	}
+	return true;
+}
+
+
+/*
+======================
+M_MoveToGoal
+======================
+*/
+void M_MoveToGoal (edict_t *ent, float dist)
+{
+	edict_t		*goal;
+	
+	goal = ent->goalentity;
+
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return;
+
+// if the next step hits the enemy, return immediately
+	if (ent->enemy &&  SV_CloseEnough (ent, ent->enemy, dist) )
+		return;
+
+// bump around...
+	if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+	{
+		if (ent->inuse)
+			SV_NewChaseDir (ent, goal, dist);
+	}
+}
+
+
+/*
+===============
+M_walkmove
+===============
+*/
+qboolean M_walkmove (edict_t *ent, float yaw, float dist)
+{
+	vec3_t	move;
+	
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return false;
+
+	yaw = yaw*M_PI*2 / 360;
+	
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	return SV_movestep(ent, move, true);
+}
--- /dev/null
+++ b/crbot/mkfile
@@ -1,0 +1,39 @@
+</$objtype/mkfile
+
+LIB=crbot.$O.a
+
+OFILES=\
+	cr_main.$O\
+	cr_menu.$O\
+	g_ai.$O\
+	g_chase.$O\
+	g_cmds.$O\
+	g_combat.$O\
+	g_ctf.$O\
+	g_func.$O\
+	g_items.$O\
+	g_main.$O\
+	g_misc.$O\
+	g_monster.$O\
+	g_phys.$O\
+	g_save.$O\
+	g_spawn.$O\
+	g_svcmds.$O\
+	g_target.$O\
+	g_trigger.$O\
+	g_utils.$O\
+	g_weapon.$O\
+	m_flash.$O\
+	m_move.$O\
+	p_client.$O\
+	p_hud.$O\
+	p_menu.$O\
+	p_trail.$O\
+	p_view.$O\
+	p_weapon.$O\
+	q_shared.$O\
+
+HFILES=\
+	game.h\
+
+</sys/src/cmd/mklib
--- /dev/null
+++ b/crbot/p_client.c
@@ -1,0 +1,1695 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+
+void SP_misc_teleporter_dest (edict_t *ent);
+
+//
+// Gross, ugly, disgustuing hack section
+//
+
+// this function is an ugly as hell hack to fix some map flaws
+//
+// the coop spawn spots on some maps are SNAFU.  There are coop spots
+// with the wrong targetname as well as spots with no name at all
+//
+// we use carnal knowledge of the maps to fix the coop spot targetnames to match
+// that of the nearest named single player spot
+
+static void SP_FixCoopSpots (edict_t *self)
+{
+	edict_t	*spot;
+	vec3_t	d;
+
+	spot = NULL;
+
+	while(1)
+	{
+		spot = G_Find(spot, FOFS(classname), "info_player_start");
+		if (!spot)
+			return;
+		if (!spot->targetname)
+			continue;
+		VectorSubtract(self->s.origin, spot->s.origin, d);
+		if (VectorLength(d) < 384)
+		{
+			if ((!self->targetname) || cistrcmp(self->targetname, spot->targetname) != 0)
+			{
+//				gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname);
+				self->targetname = spot->targetname;
+			}
+			return;
+		}
+	}
+}
+
+// now if that one wasn't ugly enough for you then try this one on for size
+// some maps don't have any coop spots at all, so we need to create them
+// where they should have been
+
+static void SP_CreateCoopSpots (edict_t *)
+{
+	edict_t	*spot;
+
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 - 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 128;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		return;
+	}
+}
+
+
+/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+The normal starting point for a level.
+*/
+void SP_info_player_start(edict_t *self)
+{
+	if (!coop->value)
+		return;
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_CreateCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for deathmatch games
+*/
+void SP_info_player_deathmatch(edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	SP_misc_teleporter_dest (self);
+}
+
+/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for coop games
+*/
+
+void SP_info_player_coop(edict_t *self)
+{
+	if (!coop->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if((cistrcmp(level.mapname, "jail2") == 0)   ||
+	   (cistrcmp(level.mapname, "jail4") == 0)   ||
+	   (cistrcmp(level.mapname, "mine1") == 0)   ||
+	   (cistrcmp(level.mapname, "mine2") == 0)   ||
+	   (cistrcmp(level.mapname, "mine3") == 0)   ||
+	   (cistrcmp(level.mapname, "mine4") == 0)   ||
+	   (cistrcmp(level.mapname, "lab") == 0)     ||
+	   (cistrcmp(level.mapname, "boss1") == 0)   ||
+	   (cistrcmp(level.mapname, "fact3") == 0)   ||
+	   (cistrcmp(level.mapname, "biggun") == 0)  ||
+	   (cistrcmp(level.mapname, "space") == 0)   ||
+	   (cistrcmp(level.mapname, "command") == 0) ||
+	   (cistrcmp(level.mapname, "power2") == 0) ||
+	   (cistrcmp(level.mapname, "strike") == 0))
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_FixCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+
+/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The deathmatch intermission point will be at one of these
+Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw.  'pitch yaw roll'
+*/
+void SP_info_player_intermission(edict_t *)
+{
+}
+
+
+//=======================================================================
+
+
+void player_pain (edict_t *, edict_t *, float, int)
+{
+	// player pain is handled at the end of the frame in P_DamageFeedback
+}
+
+
+qboolean IsFemale (edict_t *ent)
+{
+	char		*info;
+
+	if (!ent->client)
+		return false;
+
+	info = Info_ValueForKey (ent->client->pers.userinfo, "skin");
+	if (info[0] == 'f' || info[0] == 'F')
+		return true;
+	return false;
+}
+
+
+void ClientObituary (edict_t *self, edict_t *, edict_t *attacker)
+{
+	int			mod;
+	char		*message;
+	char		*message2;
+	qboolean	ff;
+
+
+	if (coop->value && attacker->client)
+		meansOfDeath |= MOD_FRIENDLY_FIRE;
+
+	if (deathmatch->value || coop->value)
+	{
+		ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+		mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+		message = NULL;
+		message2 = "";
+
+		switch (mod)
+		{
+		case MOD_SUICIDE:
+			message = "suicides";
+			break;
+		case MOD_FALLING:
+			message = "cratered";
+			break;
+		case MOD_CRUSH:
+			message = "was squished";
+			break;
+		case MOD_WATER:
+			message = "sank like a rock";
+			break;
+		case MOD_SLIME:
+			message = "melted";
+			break;
+		case MOD_LAVA:
+			message = "does a back flip into the lava";
+			break;
+		case MOD_EXPLOSIVE:
+		case MOD_BARREL:
+			message = "blew up";
+			break;
+		case MOD_EXIT:
+			message = "found a way out";
+			break;
+		case MOD_TARGET_LASER:
+			message = "saw the light";
+			break;
+		case MOD_TARGET_BLASTER:
+			message = "got blasted";
+			break;
+		case MOD_BOMB:
+		case MOD_SPLASH:
+		case MOD_TRIGGER_HURT:
+			message = "was in the wrong place";
+			break;
+		}
+		if (attacker == self)
+		{
+			switch (mod)
+			{
+			case MOD_HELD_GRENADE:
+				message = "tried to put the pin back in";
+				break;
+			case MOD_HG_SPLASH:
+			case MOD_G_SPLASH:
+				if (IsFemale(self))
+					message = "tripped on her own grenade";
+				else
+					message = "tripped on his own grenade";
+				break;
+			case MOD_R_SPLASH:
+				if (IsFemale(self))
+					message = "blew herself up";
+				else
+					message = "blew himself up";
+				break;
+			case MOD_BFG_BLAST:
+				message = "should have used a smaller gun";
+				break;
+			default:
+				if (IsFemale(self))
+					message = "killed herself";
+				else
+					message = "killed himself";
+				break;
+			}
+		}
+		if (message)
+		{
+			if (obituary_msgs->value) 
+              gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
+			if (deathmatch->value)
+			self->client->resp.score--;
+			self->enemy = NULL;
+			return;
+		}
+
+		self->enemy = attacker;
+		if (attacker && attacker->client)
+		{
+			switch (mod)
+			{
+			case MOD_BLASTER:
+				message = "was blasted by";
+				break;
+			case MOD_SHOTGUN:
+				message = "was gunned down by";
+				break;
+			case MOD_SSHOTGUN:
+				message = "was blown away by";
+				message2 = "'s super shotgun";
+				break;
+			case MOD_MACHINEGUN:
+				message = "was machinegunned by";
+				break;
+			case MOD_CHAINGUN:
+				message = "was cut in half by";
+				message2 = "'s chaingun";
+				break;
+			case MOD_GRENADE:
+				message = "was popped by";
+				message2 = "'s grenade";
+				break;
+			case MOD_G_SPLASH:
+				message = "was shredded by";
+				message2 = "'s shrapnel";
+				break;
+			case MOD_ROCKET:
+				message = "ate";
+				message2 = "'s rocket";
+				break;
+			case MOD_R_SPLASH:
+				message = "almost dodged";
+				message2 = "'s rocket";
+				break;
+			case MOD_HYPERBLASTER:
+				message = "was melted by";
+				message2 = "'s hyperblaster";
+				break;
+			case MOD_RAILGUN:
+				message = "was railed by";
+				break;
+			case MOD_BFG_LASER:
+				message = "saw the pretty lights from";
+				message2 = "'s BFG";
+				break;
+			case MOD_BFG_BLAST:
+				message = "was disintegrated by";
+				message2 = "'s BFG blast";
+				break;
+			case MOD_BFG_EFFECT:
+				message = "couldn't hide from";
+				message2 = "'s BFG";
+				break;
+			case MOD_HANDGRENADE:
+				message = "caught";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HG_SPLASH:
+				message = "didn't see";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HELD_GRENADE:
+				message = "feels";
+				message2 = "'s pain";
+				break;
+			case MOD_TELEFRAG:
+				message = "tried to invade";
+				message2 = "'s personal space";
+				break;
+//ZOID
+			case MOD_GRAPPLE:
+				message = "was caught by";
+				message2 = "'s grapple";
+				break;
+//ZOID
+
+			}
+			if (message)
+			{
+                if (obituary_msgs->value) 
+				  gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
+				if (deathmatch->value)
+				{
+				if (ff)
+					attacker->client->resp.score--;
+				else
+					attacker->client->resp.score++;
+			}
+			return;
+		}
+	}
+	}
+
+    if (obituary_msgs->value) 
+	  gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
+	if (deathmatch->value)
+	self->client->resp.score--;
+}
+
+
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void TossClientWeapon (edict_t *self)
+{
+	gitem_t		*item;
+	edict_t		*drop;
+	qboolean	quad;
+	float		spread;
+
+	if (!deathmatch->value)
+		return;
+
+	item = self->client->pers.weapon;
+	if (! self->client->pers.inventory[self->client->ammo_index] )
+		item = NULL;
+	if (item && (strcmp (item->pickup_name, "Blaster") == 0))
+		item = NULL;
+
+	if (!((int)(dmflags->value) & DF_QUAD_DROP))
+		quad = false;
+	else
+		quad = (self->client->quad_framenum > (level.framenum + 10));
+
+	if (item && quad)
+		spread = 22.5;
+	else
+		spread = 0.0;
+
+	if (item)
+	{
+		self->client->v_angle[YAW] -= spread;
+		drop = Drop_Item (self, item);
+		self->client->v_angle[YAW] += spread;
+		drop->spawnflags = DROPPED_PLAYER_ITEM;
+	}
+
+	if (quad)
+	{
+		self->client->v_angle[YAW] += spread;
+		drop = Drop_Item (self, FindItemByClassname ("item_quad"));
+		self->client->v_angle[YAW] -= spread;
+		drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+		drop->touch = Touch_Item;
+		drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
+		drop->think = G_FreeEdict;
+	}
+}
+
+
+/*
+==================
+LookAtKiller
+==================
+*/
+void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+	vec3_t		dir;
+
+	if (attacker && attacker != WORLD && attacker != self)
+	{
+		VectorSubtract (attacker->s.origin, self->s.origin, dir);
+	}
+	else if (inflictor && inflictor != WORLD && inflictor != self)
+	{
+		VectorSubtract (inflictor->s.origin, self->s.origin, dir);
+	}
+	else
+	{
+		self->client->killer_yaw = self->s.angles[YAW];
+		return;
+	}
+
+	self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
+}
+
+/*
+==================
+player_die
+==================
+*/
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	int		n;
+
+	VectorClear (self->avelocity);
+
+	self->takedamage = DAMAGE_YES;
+	self->movetype = MOVETYPE_TOSS;
+
+	self->s.modelindex2 = 0;	// remove linked weapon model
+//ZOID
+	self->s.modelindex3 = 0;	// remove linked ctf flag
+//ZOID
+
+	self->s.angles[0] = 0;
+	self->s.angles[2] = 0;
+
+	self->s.sound = 0;
+	self->client->weapon_sound = 0;
+
+	self->maxs[2] = -8;
+
+//	self->solid = SOLID_NOT;
+	self->svflags |= SVF_DEADMONSTER;
+
+	if (!self->deadflag)
+	{
+		self->client->respawn_time = level.time + 1.0;
+		LookAtKiller (self, inflictor, attacker);
+		self->client->ps.pmove.pm_type = PM_DEAD;
+		ClientObituary (self, inflictor, attacker);
+//ZOID
+		CTFFragBonuses(self, inflictor, attacker);
+//ZOID
+		TossClientWeapon (self);
+//ZOID
+		CTFPlayerResetGrapple(self);
+		CTFDeadDropFlag(self);
+		CTFDeadDropTech(self);
+//ZOID
+		if (deathmatch->value && !self->client->showscores)
+			Cmd_Help_f (self);		// show scores
+	}
+
+	// remove powerups
+	self->client->quad_framenum = 0;
+	self->client->invincible_framenum = 0;
+	self->client->breather_framenum = 0;
+	self->client->enviro_framenum = 0;
+
+	// clear inventory
+	memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory));
+
+	if (self->health < -40)
+	{	// gib
+		gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowClientHead (self, damage);
+//ZOID
+		self->client->anim_priority = ANIM_DEATH;
+		self->client->anim_end = 0;
+//ZOID
+		self->takedamage = DAMAGE_NO;
+	}
+	else
+	{	// normal death
+		if (!self->deadflag)
+		{
+			static int i;
+
+			i = (i+1)%3;
+			// start a death animation
+			self->client->anim_priority = ANIM_DEATH;
+			if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				self->s.frame = FRAME_crdeath1-1;
+				self->client->anim_end = FRAME_crdeath5;
+			}
+			else switch (i)
+			{
+			case 0:
+				self->s.frame = FRAME_death101-1;
+				self->client->anim_end = FRAME_death106;
+				break;
+			case 1:
+				self->s.frame = FRAME_death201-1;
+				self->client->anim_end = FRAME_death206;
+				break;
+			case 2:
+				self->s.frame = FRAME_death301-1;
+				self->client->anim_end = FRAME_death308;
+				break;
+			}
+			gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
+		}
+	}
+
+	self->deadflag = DEAD_DEAD;
+
+	gi.linkentity (self);
+}
+
+//=======================================================================
+
+/*
+==============
+InitClientPersistant
+
+This is only called when the game first initializes in single player,
+but is called after each death and level change in deathmatch
+==============
+*/
+void InitClientPersistant (gclient_t *client)
+{
+	gitem_t		*item;
+    int  team_no;
+
+    team_no = client->pers.team_no;
+
+	memset (&client->pers, 0, sizeof(client->pers));
+
+	item = FindItem("Blaster");
+	client->pers.selected_item = ITEM_INDEX(item);
+	client->pers.inventory[client->pers.selected_item] = 1;
+
+	client->pers.weapon = item;
+//ZOID
+	client->pers.lastweapon = item;
+//ZOID
+
+    if (!(int)no_hook->value) {
+  //ZOID
+	  item = FindItem("Grapple");
+	  client->pers.inventory[ITEM_INDEX(item)] = 1;
+  //ZOID
+      }
+
+	client->pers.health			= 100;
+	client->pers.max_health		= 100;
+
+	client->pers.max_bullets	= 200;
+	client->pers.max_shells		= 100;
+	client->pers.max_rockets	= 50;
+	client->pers.max_grenades	= 50;
+	client->pers.max_cells		= 200;
+	client->pers.max_slugs		= 50;
+
+	client->pers.connected = true;
+
+    client->pers.team_no = team_no;
+}
+
+
+void InitClientResp (gclient_t *client)
+{
+//ZOID
+	int ctf_team = client->resp.ctf_team;
+//ZOID
+
+	memset (&client->resp, 0, sizeof(client->resp));
+	
+//ZOID
+	client->resp.ctf_team = ctf_team;
+//ZOID
+
+	client->resp.enterframe = level.framenum;
+	client->resp.coop_respawn = client->pers;
+ 
+//ZOID
+	if (ctf->value && client->resp.ctf_team < CTF_TEAM1)
+		CTFAssignTeam(client);
+//ZOID
+}
+
+/*
+==================
+SaveClientData
+
+Some information that should be persistant, like health, 
+is still stored in the edict structure, so it needs to
+be mirrored out to the client structure before all the
+edicts are wiped.
+==================
+*/
+void SaveClientData (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		ent = &g_edicts[1+i];
+		if (!ent->inuse)
+			continue;
+		game.clients[i].pers.health = ent->health;
+		game.clients[i].pers.max_health = ent->max_health;
+		game.clients[i].pers.powerArmorActive = (ent->flags & FL_POWER_ARMOR);
+		if (coop->value)
+			game.clients[i].pers.score = ent->client->resp.score;
+	}
+}
+
+void FetchClientEntData (edict_t *ent)
+{
+	ent->health = ent->client->pers.health;
+	ent->max_health = ent->client->pers.max_health;
+	if (ent->client->pers.powerArmorActive)
+		ent->flags |= FL_POWER_ARMOR;
+	if (coop->value)
+		ent->client->resp.score = ent->client->pers.score;
+}
+
+
+
+/*
+=======================================================================
+
+  SelectSpawnPoint
+
+=======================================================================
+*/
+
+/*
+================
+PlayersRangeFromSpot
+
+Returns the distance to the nearest player from the given spot
+================
+*/
+float	PlayersRangeFromSpot (edict_t *spot)
+{
+	edict_t	*player;
+	float	bestplayerdistance;
+	vec3_t	v;
+	int		n;
+	float	playerdistance;
+
+
+	bestplayerdistance = 9999999;
+
+	for (n = 1; n <= maxclients->value; n++)
+	{
+		player = &g_edicts[n];
+
+		if (!player->inuse)
+			continue;
+
+		if (player->health <= 0)
+			continue;
+
+		VectorSubtract (spot->s.origin, player->s.origin, v);
+		playerdistance = VectorLength (v);
+
+		if (playerdistance < bestplayerdistance)
+			bestplayerdistance = playerdistance;
+	}
+
+	return bestplayerdistance;
+}
+
+/*
+================
+SelectRandomDeathmatchSpawnPoint
+
+go to a random point, but NOT the two points closest
+to other players
+================
+*/
+edict_t *SelectRandomDeathmatchSpawnPoint (void)
+{
+	edict_t	*spot, *spot1, *spot2;
+	int		count = 0;
+	int		selection;
+	float	range, range1, range2;
+
+	spot = NULL;
+	range1 = range2 = 99999;
+	spot1 = spot2 = NULL;
+
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		count++;
+		range = PlayersRangeFromSpot(spot);
+		if (range < range1)
+		{
+			range1 = range;
+			spot1 = spot;
+		}
+		else if (range < range2)
+		{
+			range2 = range;
+			spot2 = spot;
+		}
+	}
+
+	if (!count)
+		return NULL;
+
+	if (count <= 2)
+	{
+		spot1 = spot2 = NULL;
+	}
+	else
+		count -= 2;
+
+	selection = rand() % count;
+
+	spot = NULL;
+	do
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+		if (spot == spot1 || spot == spot2)
+			selection++;
+	} while(selection--);
+
+	return spot;
+}
+
+/*
+================
+SelectFarthestDeathmatchSpawnPoint
+
+================
+*/
+edict_t *SelectFarthestDeathmatchSpawnPoint (void)
+{
+	edict_t	*bestspot;
+	float	bestdistance, bestplayerdistance;
+	edict_t	*spot;
+
+
+	spot = NULL;
+	bestspot = NULL;
+	bestdistance = 0;
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		bestplayerdistance = PlayersRangeFromSpot (spot);
+
+		if (bestplayerdistance > bestdistance)
+		{
+			bestspot = spot;
+			bestdistance = bestplayerdistance;
+		}
+	}
+
+	if (bestspot)
+	{
+		return bestspot;
+	}
+
+	// if there is a player just spawned on each and every start spot
+	// we have no choice to turn one into a telefrag meltdown
+	spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+
+	return spot;
+}
+
+edict_t *SelectDeathmatchSpawnPoint (void)
+{
+	if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
+		return SelectFarthestDeathmatchSpawnPoint ();
+	else
+		return SelectRandomDeathmatchSpawnPoint ();
+}
+
+
+edict_t *SelectCoopSpawnPoint (edict_t *ent)
+{
+	int		index;
+	edict_t	*spot;
+	char	*target;
+
+	index = ent->client - game.clients;
+
+	// player 0 starts in normal player spawn point
+	if (!index)
+		return NULL;
+
+	spot = NULL;
+
+	// assume there are four coop spots at each spawnpoint
+	while (1)
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_coop");
+		if (!spot)
+			return NULL;	// we didn't have enough...
+
+		target = spot->targetname;
+		if (!target)
+			target = "";
+		if ( cistrcmp(game.spawnpoint, target) == 0 )
+		{	// this is a coop spawn point for one of the clients here
+			index--;
+			if (!index)
+				return spot;		// this is it
+		}
+	}
+}
+
+
+/*
+===========
+SelectSpawnPoint
+
+Chooses a player start, deathmatch start, coop start, etc
+============
+*/
+void	SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
+{
+	edict_t	*spot = NULL;
+
+	if (deathmatch->value)
+//ZOID
+		if (ctf->value)
+			spot = SelectCTFSpawnPoint(ent);
+		else
+//ZOID
+			spot = SelectDeathmatchSpawnPoint ();
+	else if (coop->value)
+		spot = SelectCoopSpawnPoint (ent);
+
+	// find a single player start spot
+	if (!spot)
+	{
+		while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL)
+		{
+			if (!game.spawnpoint[0] && !spot->targetname)
+				break;
+
+			if (!game.spawnpoint[0] || !spot->targetname)
+				continue;
+
+			if (cistrcmp(game.spawnpoint, spot->targetname) == 0)
+				break;
+		}
+
+		if (!spot)
+		{
+			if (!game.spawnpoint[0])
+			{	// there wasn't a spawnpoint without a target, so use any
+				spot = G_Find (spot, FOFS(classname), "info_player_start");
+			}
+			if (!spot)
+				gi.error ("Couldn't find spawn point %s\n", game.spawnpoint);
+		}
+	}
+
+	VectorCopy (spot->s.origin, origin);
+	origin[2] += 9;
+	VectorCopy (spot->s.angles, angles);
+}
+
+//======================================================================
+
+
+void InitBodyQue (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.body_que = 0;
+	for (i=0; i<BODY_QUEUE_SIZE ; i++)
+	{
+		ent = G_Spawn();
+		ent->classname = "bodyque";
+	}
+}
+
+void body_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int	n;
+
+	if (self->health < -40)
+	{
+		gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->s.origin[2] -= 48;
+		ThrowClientHead (self, damage);
+		self->takedamage = DAMAGE_NO;
+	}
+}
+
+void CopyToBodyQue (edict_t *ent)
+{
+	edict_t		*body;
+
+
+	// grab a body que and cycle to the next one
+	body = &g_edicts[(int)maxclients->value + level.body_que + 1];
+	level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+	// FIXME: send an effect on the removed body
+
+	gi.unlinkentity (ent);
+
+	gi.unlinkentity (body);
+	body->s = ent->s;
+	body->s.number = body - g_edicts;
+
+	body->svflags = ent->svflags;
+	VectorCopy (ent->mins, body->mins);
+	VectorCopy (ent->maxs, body->maxs);
+	VectorCopy (ent->absmin, body->absmin);
+	VectorCopy (ent->absmax, body->absmax);
+	VectorCopy (ent->size, body->size);
+	body->solid = ent->solid;
+	body->clipmask = ent->clipmask;
+	body->owner = ent->owner;
+	body->movetype = ent->movetype;
+
+	body->die = body_die;
+	body->takedamage = DAMAGE_YES;
+
+	gi.linkentity (body);
+}
+
+
+void respawn (edict_t *self)
+{
+    if (self->bot_info) return;
+
+	if (deathmatch->value || coop->value)
+	{
+		CopyToBodyQue (self);
+		PutClientInServer (self);
+
+		// add a teleportation effect
+		self->s.event = EV_PLAYER_TELEPORT;
+
+		// hold in place briefly
+		self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		self->client->ps.pmove.pm_time = 14;
+
+		self->client->respawn_time = level.time;
+
+		return;
+	}
+
+	// restart the entire server
+	gi.AddCommandString ("menu_loadgame\n");
+}
+
+//==============================================================
+
+
+/*
+===========
+PutClientInServer
+
+Called when a player connects to a server or respawns in
+a deathmatch.
+============
+*/
+void PutClientInServer (edict_t *ent)
+{
+	vec3_t	mins = {-16, -16, -24};
+	vec3_t	maxs = {16, 16, 32};
+	int		index;
+	vec3_t	spawn_origin, spawn_angles;
+	gclient_t	*client;
+	int		i;
+	client_persistant_t	saved;
+	client_respawn_t	resp;
+
+	// find a spawn point
+	// do it before setting health back up, so farthest
+	// ranging doesn't count this client
+	SelectSpawnPoint (ent, spawn_origin, spawn_angles);
+
+	index = ent-g_edicts-1;
+	client = ent->client;
+
+	// deathmatch wipes most client data every spawn
+	if (deathmatch->value)
+	{
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		InitClientPersistant (client);
+		ClientUserinfoChanged (ent, userinfo);
+	}
+	else if (coop->value)
+	{
+		int			n;
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		// this is kind of ugly, but it's how we want to handle keys in coop
+		for (n = 0; n < MAX_ITEMS; n++)
+		{
+			if (itemlist[n].flags & IT_KEY)
+				resp.coop_respawn.inventory[n] = client->pers.inventory[n];
+		}
+		client->pers = resp.coop_respawn;
+		ClientUserinfoChanged (ent, userinfo);
+		if (resp.score > client->pers.score)
+			client->pers.score = resp.score;
+	}
+	else
+	{
+		memset (&resp, 0, sizeof(resp));
+	}
+
+	// clear everything but the persistant data
+	saved = client->pers;
+	memset (client, 0, sizeof(*client));
+	client->pers = saved;
+	if (client->pers.health <= 0)
+		InitClientPersistant(client);
+	client->resp = resp;
+
+   // mark this client slot as used
+    client->inuse = true;
+
+	// copy some data from the client to the entity
+	FetchClientEntData (ent);
+
+	// clear entity values
+	ent->groundentity = NULL;
+	ent->client = &game.clients[index];
+	ent->takedamage = DAMAGE_AIM;
+	ent->movetype = MOVETYPE_WALK;
+	ent->viewheight = 22;
+	ent->inuse = true;
+	ent->classname = "player";
+	ent->mass = 200;
+	ent->solid = SOLID_BBOX;
+	ent->deadflag = DEAD_NO;
+	ent->air_finished = level.time + 12;
+	ent->clipmask = MASK_PLAYERSOLID;
+	ent->model = "players/male/tris.md2";
+	ent->pain = player_pain;
+	ent->die = player_die;
+	ent->waterlevel = 0;
+	ent->watertype = 0;
+	ent->flags &= ~FL_NO_KNOCKBACK;
+	ent->svflags &= ~SVF_DEADMONSTER;
+
+	VectorCopy (mins, ent->mins);
+	VectorCopy (maxs, ent->maxs);
+	VectorClear (ent->velocity);
+
+	// clear playerstate values
+	memset (&ent->client->ps, 0, sizeof(client->ps));
+
+	client->ps.pmove.origin[0] = spawn_origin[0]*8;
+	client->ps.pmove.origin[1] = spawn_origin[1]*8;
+	client->ps.pmove.origin[2] = spawn_origin[2]*8;
+//ZOID
+	client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+//ZOID
+
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		client->ps.fov = 90;
+	}
+	else
+	{
+		client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+		if (client->ps.fov < 1)
+			client->ps.fov = 90;
+		else if (client->ps.fov > 160)
+			client->ps.fov = 160;
+	}
+
+	client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+
+	// clear entity state values
+	ent->s.effects = 0;
+	ent->s.skinnum = ent - g_edicts - 1;
+	ent->s.modelindex = 255;		// will use the skin specified model
+
+	ent->s.modelindex2 = 255;		// custom gun model
+	// sknum is player num and weapon number
+	// weapon number will be added in changeweapon
+	ent->s.skinnum = ent - g_edicts - 1;
+
+	ent->s.frame = 0;
+	VectorCopy (spawn_origin, ent->s.origin);
+	ent->s.origin[2] += 1;	// make sure off ground
+	VectorCopy (ent->s.origin, ent->s.old_origin);
+
+	// set the delta angle
+	for (i=0 ; i<3 ; i++)
+		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
+
+	ent->s.angles[PITCH] = 0;
+	ent->s.angles[YAW] = spawn_angles[YAW];
+	ent->s.angles[ROLL] = 0;
+	VectorCopy (ent->s.angles, client->ps.viewangles);
+	VectorCopy (ent->s.angles, client->v_angle);
+
+//ZOID
+	if (CTFStartClient(ent))
+		return;
+//ZOID
+
+	if (!KillBox (ent))
+	{	// could't spawn in?
+	}
+
+	gi.linkentity (ent);
+
+	// force the current weapon up
+	client->newweapon = client->pers.weapon;
+	ChangeWeapon (ent);
+}
+
+/*
+=====================
+ClientBeginDeathmatch
+
+A client has just connected to the server in 
+deathmatch mode, so clear everything out before starting them.
+=====================
+*/
+void ClientBeginDeathmatch (edict_t *ent)
+{
+	G_InitEdict (ent);
+
+	InitClientResp (ent->client);
+
+	// locate ent at a spawn point
+	PutClientInServer (ent);
+
+	if (!ent->bot_info && !((int)ctf->value)) gi.centerprintf( ent, CRBOT_INFO );
+
+	// send effect
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_LOGIN);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+
+/*
+===========
+ClientBegin
+
+called when a client has finished connecting, and is ready
+to be placed into the game.  This will happen every level load.
+============
+*/
+void ClientBegin (edict_t *ent)
+{
+	int		i;
+
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	if (deathmatch->value)
+	{
+		ClientBeginDeathmatch (ent);
+		return;
+	}
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == true)
+	{
+		// the client has cleared the client side viewangles upon
+		// connecting to the server, which is different than the
+		// state when the game is saved, so we need to compensate
+		// with deltaangles
+		for (i=0 ; i<3 ; i++)
+			ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
+	}
+	else
+	{
+		// a spawn point will completely reinitialize the entity
+		// except for the persistant data that was initialized at
+		// ClientConnect() time
+		G_InitEdict (ent);
+		ent->classname = "player";
+		InitClientResp (ent->client);
+		PutClientInServer (ent);
+	}
+
+	if (level.intermissiontime)
+	{
+		MoveClientToIntermission (ent);
+	}
+	else
+	{
+		// send effect if in a multiplayer game
+		if (game.maxclients > 1)
+		{
+			gi.WriteByte (svc_muzzleflash);
+			gi.WriteShort (ent-g_edicts);
+			gi.WriteByte (MZ_LOGIN);
+			gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+			gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+		}
+	}
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+/*
+===========
+ClientUserInfoChanged
+
+called whenever the player updates a userinfo variable.
+
+The game can override any of the settings in place
+(forcing skins or names, etc) before copying it off.
+============
+*/
+void ClientUserinfoChanged (edict_t *ent, char *userinfo)
+{
+	char	*s;
+	int		playernum;
+
+	// check for malformed or illegal info strings
+	if (!Info_Validate(userinfo))
+	{
+		strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt");
+	}
+
+	// set name
+	s = Info_ValueForKey (userinfo, "name");
+	strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
+
+	// set skin
+	s = Info_ValueForKey (userinfo, "skin");
+
+	playernum = ent-g_edicts-1;
+
+	// combine name and skin into a configstring
+//ZOID
+	if (ctf->value)
+		CTFAssignSkin(ent, s);
+	else
+//ZOID
+		gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
+
+	// fov
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		ent->client->ps.fov = 90;
+	}
+	else
+	{
+		ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
+		if (ent->client->ps.fov < 1)
+			ent->client->ps.fov = 90;
+		else if (ent->client->ps.fov > 160)
+			ent->client->ps.fov = 160;
+	}
+
+	// handedness
+	s = Info_ValueForKey (userinfo, "hand");
+	if (strlen(s))
+	{
+		ent->client->pers.hand = atoi(s);
+	}
+
+	// save off the userinfo in case we want to check something later
+	strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
+}
+
+
+/*
+===========
+ClientConnect
+
+Called when a player begins connecting to the server.
+The game can refuse entrance to a client by returning false.
+If the client is allowed, the connection process will continue
+and eventually get to ClientBegin()
+Changing levels will NOT cause this to be called again, but
+loadgames will.
+============
+*/
+qboolean ClientConnect (edict_t *ent, char *userinfo)
+{
+	char	*value;
+
+	// check to see if they are on the banned IP list
+	//value = Info_ValueForKey (userinfo, "ip");
+
+	// check for a password
+	value = Info_ValueForKey (userinfo, "password");
+	if (strcmp(password->string, value) != 0)
+		return false;
+
+    cr_client_connect(ent);
+
+	// they can connect
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == false)
+	{
+		// clear the respawning variables
+//ZOID -- force team join
+		ent->client->resp.ctf_team = -1;
+//ZOID
+		InitClientResp (ent->client);
+		if (!game.autosaved || !ent->client->pers.weapon)
+			InitClientPersistant (ent->client);
+	}
+
+    ent->client->inuse = true;
+
+	ClientUserinfoChanged (ent, userinfo);
+
+	if (game.maxclients > 1)
+		gi.dprintf ("%s connected\n", ent->client->pers.netname);
+
+	ent->client->pers.connected = true;
+	return true;
+}
+
+/*
+===========
+ClientDisconnect
+
+Called when a player drops from the server.
+Will not be called between levels.
+============
+*/
+void ClientDisconnect (edict_t *ent)
+{
+	int		playernum;
+
+	if (!ent->client)
+		return;
+
+	gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+//ZOID
+	CTFDeadDropFlag(ent);
+	CTFDeadDropTech(ent);
+//ZOID
+
+	// send effect
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_LOGOUT);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	gi.unlinkentity (ent);
+	ent->s.modelindex = 0;
+	ent->solid = SOLID_NOT;
+	ent->inuse = false;
+	ent->classname = "disconnected";
+	ent->client->pers.connected = false;
+
+	playernum = ent-g_edicts-1;
+	gi.configstring (CS_PLAYERSKINS+playernum, "");
+}
+
+
+//==============================================================
+
+
+edict_t	*pm_passent;
+
+// pmove doesn't need to know about passent and contentmask
+trace_t	PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
+{
+	if (pm_passent->health > 0)
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
+	else
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
+}
+
+unsigned CheckBlock (void *b, int c)
+{
+	int	v,i;
+	v = 0;
+	for (i=0 ; i<c ; i++)
+		v+= ((byte *)b)[i];
+	return v;
+}
+void PrintPmove (pmove_t *pm)
+{
+	unsigned	c1, c2;
+
+	c1 = CheckBlock (&pm->s, sizeof(pm->s));
+	c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd));
+	Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
+}
+
+/*
+==============
+ClientThink
+
+This will be called once for each client frame, which will
+usually be a couple times for each server frame.
+==============
+*/
+void ClientThink (edict_t *ent, usercmd_t *ucmd)
+{
+	gclient_t	*client;
+	edict_t	*other;
+	int		i, j;
+	pmove_t	pm;
+
+	level.current_entity = ent;
+	client = ent->client;
+
+	if (level.intermissiontime)
+	{
+		client->ps.pmove.pm_type = PM_FREEZE;
+		// can exit intermission after five seconds
+		if (level.time > level.intermissiontime + 5.0 
+			&& (ucmd->buttons & BUTTON_ANY) )
+			level.exitintermission = true;
+		return;
+	}
+
+	pm_passent = ent;
+
+//ZOID
+	if (ent->client->chase_target) {
+		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+		return;
+	}
+//ZOID
+
+	// set up for pmove
+	memset (&pm, 0, sizeof(pm));
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+		client->ps.pmove.pm_type = PM_SPECTATOR;
+	else if (ent->s.modelindex != 255)
+		client->ps.pmove.pm_type = PM_GIB;
+	else if (ent->deadflag)
+		client->ps.pmove.pm_type = PM_DEAD;
+	else
+		client->ps.pmove.pm_type = PM_NORMAL;
+
+	client->ps.pmove.gravity = sv_gravity->value;
+	pm.s = client->ps.pmove;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		pm.s.origin[i] = ent->s.origin[i]*8;
+		pm.s.velocity[i] = ent->velocity[i]*8;
+	}
+
+	if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+	{
+		pm.snapinitial = true;
+//		gi.dprintf ("pmove changed!\n");
+	}
+
+	pm.cmd = *ucmd;
+
+	pm.trace = PM_trace;	// adds default parms
+	pm.pointcontents = gi.pointcontents;
+
+	// perform a pmove
+	gi.Pmove (&pm);
+
+	// save results of pmove
+	client->ps.pmove = pm.s;
+	client->old_pmove = pm.s;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->s.origin[i] = pm.s.origin[i]*0.125;
+		ent->velocity[i] = pm.s.velocity[i]*0.125;
+	}
+
+	VectorCopy (pm.mins, ent->mins);
+	VectorCopy (pm.maxs, ent->maxs);
+
+	client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+	client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+	client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+	if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
+	{
+		gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+		PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+	}
+
+	ent->viewheight = pm.viewheight;
+	ent->waterlevel = pm.waterlevel;
+	ent->watertype = pm.watertype;
+	ent->groundentity = pm.groundentity;
+	if (pm.groundentity)
+		ent->groundentity_linkcount = pm.groundentity->linkcount;
+
+	if (ent->deadflag)
+	{
+		client->ps.viewangles[ROLL] = 40;
+		client->ps.viewangles[PITCH] = -15;
+		client->ps.viewangles[YAW] = client->killer_yaw;
+	}
+	else
+	{
+		VectorCopy (pm.viewangles, client->v_angle);
+		VectorCopy (pm.viewangles, client->ps.viewangles);
+	}
+
+//ZOID
+	if (client->ctf_grapple)
+		CTFGrapplePull(client->ctf_grapple);
+//ZOID
+
+	gi.linkentity (ent);
+
+	if (ent->movetype != MOVETYPE_NOCLIP)
+		G_TouchTriggers (ent);
+
+	// touch other objects
+	for (i=0 ; i<pm.numtouch ; i++)
+	{
+		other = pm.touchents[i];
+		for (j=0 ; j<i ; j++)
+			if (pm.touchents[j] == other)
+				break;
+		if (j != i)
+			continue;	// duplicated
+		if (!other->touch)
+			continue;
+		other->touch (other, ent, NULL, NULL);
+	}
+
+
+	client->oldbuttons = client->buttons;
+	client->buttons = ucmd->buttons;
+	client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+	// save light level the player is standing on for
+	// monster sighting AI
+	ent->light_level = ucmd->lightlevel;
+
+	// fire weapon from final position if needed
+	if (client->latched_buttons & BUTTON_ATTACK
+//ZOID
+		&& ent->movetype != MOVETYPE_NOCLIP
+//ZOID
+		)
+	{
+		if (!client->weapon_thunk)
+		{
+			client->weapon_thunk = true;
+			Think_Weapon (ent);
+		}
+	}
+
+//ZOID
+//regen tech
+	CTFApplyRegeneration(ent);
+//ZOID
+
+//ZOID
+	for (i = 1; i <= maxclients->value; i++) {
+		other = g_edicts + i;
+		if (other->inuse && other->client->chase_target == ent)
+			UpdateChaseCam(other);
+	}
+//ZOID
+}
+
+
+/*
+==============
+ClientBeginServerFrame
+
+This will be called once for each server frame, before running
+any other entities in the world.
+==============
+*/
+void ClientBeginServerFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	int			buttonMask;
+
+	if (level.intermissiontime)
+		return;
+
+	client = ent->client;
+
+	// run weapon animations if it hasn't been done by a ucmd_t
+	if (!client->weapon_thunk
+//ZOID
+		&& ent->movetype != MOVETYPE_NOCLIP
+//ZOID
+		)
+		Think_Weapon (ent);
+	else
+		client->weapon_thunk = false;
+
+	if (ent->deadflag)
+	{
+		// wait for any button just going down
+		if ( level.time > client->respawn_time)
+		{
+			// in deathmatch, only wait for attack button
+			if (deathmatch->value)
+				buttonMask = BUTTON_ATTACK;
+			else
+				buttonMask = -1;
+
+			if ( ( client->latched_buttons & buttonMask ) ||
+				(deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) )
+			{
+				respawn(ent);
+				client->latched_buttons = 0;
+			}
+		}
+		return;
+	}
+
+	// add player trail so monsters can follow
+	if (!deathmatch->value)
+		if (!visible (ent, PlayerTrail_LastSpot() ) )
+			PlayerTrail_Add (ent->s.old_origin);
+
+	client->latched_buttons = 0;
+}
--- /dev/null
+++ b/crbot/p_hud.c
@@ -1,0 +1,527 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+======================================================================
+
+INTERMISSION
+
+======================================================================
+*/
+
+void MoveClientToIntermission (edict_t *ent)
+{
+    if (ent->bot_info) return;
+
+	if (deathmatch->value || coop->value)
+		ent->client->showscores = true;
+	VectorCopy (level.intermission_origin, ent->s.origin);
+	ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
+	ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
+	ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
+	VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
+	ent->client->ps.pmove.pm_type = PM_FREEZE;
+	ent->client->ps.gunindex = 0;
+	ent->client->ps.blend[3] = 0;
+	ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	// clean up powerup info
+	ent->client->quad_framenum = 0;
+	ent->client->invincible_framenum = 0;
+	ent->client->breather_framenum = 0;
+	ent->client->enviro_framenum = 0;
+	ent->client->grenade_blew_up = false;
+	ent->client->grenade_time = 0;
+
+	ent->viewheight = 0;
+	ent->s.modelindex = 0;
+	ent->s.modelindex2 = 0;
+	ent->s.modelindex3 = 0;
+	ent->s.modelindex = 0;
+	ent->s.effects = 0;
+	ent->s.sound = 0;
+	ent->solid = SOLID_NOT;
+
+	// add the layout
+
+	if (deathmatch->value || coop->value)
+	{
+		DeathmatchScoreboardMessage (ent, NULL);
+		gi.unicast (ent, true);
+	}
+
+}
+
+void BeginIntermission (edict_t *targ)
+{
+	int		i, n;
+	edict_t	*ent, *client;
+
+	if (level.intermissiontime)
+		return;		// allready activated
+
+//ZOID
+	if (deathmatch->value && ctf->value)
+		CTFCalcScores();
+//ZOID
+
+	game.autosaved = false;
+
+	// respawn any dead clients
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		if (client->health <= 0)
+			respawn(client);
+	}
+
+	level.intermissiontime = level.time;
+	level.changemap = targ->map;
+
+	if (strstr(level.changemap, "*"))
+	{
+		if (coop->value)
+		{
+			for (i=0 ; i<maxclients->value ; i++)
+			{
+				client = g_edicts + 1 + i;
+				if (!client->inuse)
+					continue;
+				// strip players of all keys between units
+				for (n = 0; n < MAX_ITEMS; n++)
+				{
+					if (itemlist[n].flags & IT_KEY)
+						client->client->pers.inventory[n] = 0;
+				}
+			}
+		}
+	}
+	else
+	{
+		if (!deathmatch->value)
+		{
+			level.exitintermission = 1;		// go immediately to the next level
+			return;
+		}
+	}
+
+	level.exitintermission = 0;
+
+	// find an intermission spot
+	ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
+	if (!ent)
+	{	// the map creator forgot to put in an intermission point...
+		ent = G_Find (NULL, FOFS(classname), "info_player_start");
+		if (!ent)
+			ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+	}
+	else
+	{	// chose one of four spots
+		i = rand() & 3;
+		while (i--)
+		{
+			ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+			if (!ent)	// wrap around the list
+				ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+		}
+	}
+
+	VectorCopy (ent->s.origin, level.intermission_origin);
+	VectorCopy (ent->s.angles, level.intermission_angle);
+
+	// move all clients to the intermission point
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		MoveClientToIntermission (client);
+	}
+}
+
+
+/*
+==================
+DeathmatchScoreboardMessage
+
+==================
+*/
+void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
+{
+	char	entry[1024];
+	char	string[1400];
+	int		stringlength;
+	int		i, j, k;
+	int		sorted[MAX_CLIENTS];
+	int		sortedscores[MAX_CLIENTS];
+	int		score, total;
+	int		x, y;
+	gclient_t	*cl;
+	edict_t		*cl_ent;
+	char	*tag;
+
+//ZOID
+	if (ctf->value) {
+		CTFScoreboardMessage (ent, killer);
+		return;
+	}
+//ZOID
+
+	// sort the clients by score
+	total = 0;
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		cl_ent = g_edicts + 1 + i;
+		if (!cl_ent->inuse)
+			continue;
+		score = game.clients[i].resp.score;
+		for (j=0 ; j<total ; j++)
+		{
+			if (score > sortedscores[j])
+				break;
+		}
+		for (k=total ; k>j ; k--)
+		{
+			sorted[k] = sorted[k-1];
+			sortedscores[k] = sortedscores[k-1];
+		}
+		sorted[j] = i;
+		sortedscores[j] = score;
+		total++;
+	}
+
+	// print level name and exit rules
+	string[0] = 0;
+
+	stringlength = strlen(string);
+
+	// add the clients in sorted order
+	if (total > 12)
+		total = 12;
+
+	for (i=0 ; i<total ; i++)
+	{
+		cl = &game.clients[sorted[i]];
+		cl_ent = g_edicts + 1 + sorted[i];
+
+		x = (i>=6) ? 160 : 0;
+		y = 32 + 32 * (i%6);
+
+		// add a dogtag
+		if (cl_ent == ent)
+			tag = "tag1";
+		else if (cl_ent == killer)
+			tag = "tag2";
+		else
+			tag = NULL;
+		if (tag)
+		{
+			Com_sprintf (entry, sizeof(entry),
+				"xv %i yv %i picn %s ",x+32, y, tag);
+			j = strlen(entry);
+			if (stringlength + j > 1024)
+				break;
+			strcpy (string + stringlength, entry);
+			stringlength += j;
+		}
+
+		// send the layout
+		Com_sprintf (entry, sizeof(entry),
+			"client %i %i %i %i %i %i ",
+			x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
+		j = strlen(entry);
+		if (stringlength + j > 1024)
+			break;
+		strcpy (string + stringlength, entry);
+		stringlength += j;
+	}
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+}
+
+
+/*
+==================
+DeathmatchScoreboard
+
+Draw instead of help message.
+Note that it isn't that hard to overflow the 1400 byte message limit!
+==================
+*/
+void DeathmatchScoreboard (edict_t *ent)
+{
+	DeathmatchScoreboardMessage (ent, ent->enemy);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Score_f
+
+Display the scoreboard
+==================
+*/
+void Cmd_Score_f (edict_t *ent)
+{
+	ent->client->showinventory = false;
+	ent->client->showhelp = false;
+//ZOID
+	if (ent->client->menu)
+		PMenu_Close(ent);
+//ZOID
+
+	if (!deathmatch->value && !coop->value)
+		return;
+
+	if (ent->client->showscores)
+	{
+		ent->client->showscores = false;
+		ent->client->update_chase = true;
+		return;
+	}
+
+	ent->client->showscores = true;
+
+	DeathmatchScoreboard (ent);
+}
+
+
+/*
+==================
+HelpComputer
+
+Draw help computer.
+==================
+*/
+void HelpComputer (edict_t *ent)
+{
+	char	string[1024];
+	char	*sk;
+
+	if (skill->value == 0)
+		sk = "easy";
+	else if (skill->value == 1)
+		sk = "medium";
+	else if (skill->value == 2)
+		sk = "hard";
+	else
+		sk = "hard+";
+
+	// send the layout
+	Com_sprintf (string, sizeof(string),
+		"xv 32 yv 8 picn help "			// background
+		"xv 202 yv 12 string2 \"%s\" "		// skill
+		"xv 0 yv 24 cstring2 \"%s\" "		// level name
+		"xv 0 yv 54 cstring2 \"%s\" "		// help 1
+		"xv 0 yv 110 cstring2 \"%s\" "		// help 2
+		"xv 50 yv 164 string2 \" kills     goals    secrets\" "
+		"xv 50 yv 172 string2 \"%3i/%3i     %i/%i       %i/%i\" ", 
+		sk,
+		level.level_name,
+		game.helpmessage1,
+		game.helpmessage2,
+		level.killed_monsters, level.total_monsters, 
+		level.found_goals, level.total_goals,
+		level.found_secrets, level.total_secrets);
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Help_f
+
+Display the current help message
+==================
+*/
+void Cmd_Help_f (edict_t *ent)
+{
+	// this is for backwards compatability
+	if (deathmatch->value)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+
+	ent->client->showinventory = false;
+	ent->client->showscores = false;
+
+	if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged))
+	{
+		ent->client->showhelp = false;
+		return;
+	}
+
+	ent->client->showhelp = true;
+	ent->client->resp.helpchanged = 0;
+	HelpComputer (ent);
+}
+
+
+//=======================================================================
+
+/*
+===============
+G_SetStats
+===============
+*/
+void G_SetStats (edict_t *ent)
+{
+	gitem_t		*item;
+	int			index, cells = 0;
+	int			power_armor_type;
+
+	//
+	// health
+	//
+	ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
+	ent->client->ps.stats[STAT_HEALTH] = ent->health;
+
+	//
+	// ammo
+	//
+	if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
+	{
+		ent->client->ps.stats[STAT_AMMO_ICON] = 0;
+		ent->client->ps.stats[STAT_AMMO] = 0;
+	}
+	else
+	{
+		item = &itemlist[ent->client->ammo_index];
+		ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
+	}
+	
+	//
+	// armor
+	//
+	power_armor_type = PowerArmorType (ent);
+	if (power_armor_type)
+	{
+		cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
+		if (cells == 0)
+		{	// ran out of cells for power armor
+			ent->flags &= ~FL_POWER_ARMOR;
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+			power_armor_type = 0;;
+		}
+	}
+
+	index = ArmorIndex (ent);
+	if (power_armor_type && (!index || (level.framenum & 8) ) )
+	{	// flash between power armor and other armor icon
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
+		ent->client->ps.stats[STAT_ARMOR] = cells;
+	}
+	else if (index)
+	{
+		item = GetItemByIndex (index);
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
+	}
+	else
+	{
+		ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
+		ent->client->ps.stats[STAT_ARMOR] = 0;
+	}
+
+	//
+	// pickup message
+	//
+	if (level.time > ent->client->pickup_msg_time)
+	{
+		ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
+		ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
+	}
+
+	//
+	// timers
+	//
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
+	}
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
+	}
+	else
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = 0;
+		ent->client->ps.stats[STAT_TIMER] = 0;
+	}
+
+	//
+	// selected item
+	//
+	if (ent->client->pers.selected_item == -1)
+		ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
+	else
+		ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
+
+	ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
+
+	//
+	// layouts
+	//
+	ent->client->ps.stats[STAT_LAYOUTS] = 0;
+
+	if (deathmatch->value)
+	{
+		if (ent->client->pers.health <= 0 || level.intermissiontime
+			|| ent->client->showscores)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+	else
+	{
+		if (ent->client->showscores || ent->client->showhelp)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+
+	//
+	// frags
+	//
+	ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
+
+	//
+	// help icon / current weapon if not shown
+	//
+	if (ent->client->resp.helpchanged && (level.framenum&8) )
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
+	else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
+		&& ent->client->pers.weapon)
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
+	else
+		ent->client->ps.stats[STAT_HELPICON] = 0;
+
+//ZOID
+	SetCTFStats(ent);
+//ZOID
+}
+
--- /dev/null
+++ b/crbot/p_menu.c
@@ -1,0 +1,192 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num)
+{
+	pmenuhnd_t *hnd;
+	pmenu_t *p;
+	int i;
+
+	if (!ent->client)
+		return;
+
+	if (ent->client->menu) {
+		gi.dprintf("warning, ent already has a menu\n");
+		PMenu_Close(ent);
+	}
+
+	hnd = malloc(sizeof(*hnd));
+
+	hnd->entries = entries;
+	hnd->num = num;
+
+	if (cur < 0 || !entries[cur].SelectFunc) {
+		for (i = 0, p = entries; i < num; i++, p++)
+			if (p->SelectFunc)
+				break;
+	} else
+		i = cur;
+
+	if (i >= num)
+		hnd->cur = -1;
+	else
+		hnd->cur = i;
+
+	ent->client->showscores = true;
+	ent->client->inmenu = true;
+	ent->client->menu = hnd;
+
+	PMenu_Update(ent);
+	gi.unicast (ent, true);
+}
+
+void PMenu_Close(edict_t *ent)
+{
+	if (!ent->client->menu)
+		return;
+
+	free(ent->client->menu);
+	ent->client->menu = NULL;
+	ent->client->showscores = false;
+}
+
+void PMenu_Update(edict_t *ent)
+{
+	char string[1400];
+	int i;
+	pmenu_t *p;
+	int x;
+	pmenuhnd_t *hnd;
+	char *t;
+	qboolean alt = false;
+
+	if (!ent->client->menu) {
+		gi.dprintf("warning:  ent has no menu\n");
+		return;
+	}
+
+	hnd = ent->client->menu;
+
+	strcpy(string, "xv 32 yv 8 picn inventory ");
+
+	for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) {
+		if (!p->text || !*(p->text))
+			continue; // blank line
+		t = p->text;
+		if (*t == '*') {
+			alt = true;
+			t++;
+		}
+		sprintf(string + strlen(string), "yv %d ", 32 + i * 8);
+		if (p->align == PMENU_ALIGN_CENTER)
+			x = 196/2 - strlen(t)*4 + 64;
+		else if (p->align == PMENU_ALIGN_RIGHT)
+			x = 64 + (196 - strlen(t)*8);
+		else
+			x = 64;
+
+		sprintf(string + strlen(string), "xv %d ",
+			x - ((hnd->cur == i) ? 8 : 0));
+
+		if (hnd->cur == i)
+			sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t);
+		else if (alt)
+			sprintf(string + strlen(string), "string2 \"%s\" ", t);
+		else
+			sprintf(string + strlen(string), "string \"%s\" ", t);
+		alt = false;
+	}
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+}
+
+void PMenu_Next(edict_t *ent)
+{
+	pmenuhnd_t *hnd;
+	int i;
+	pmenu_t *p;
+
+	if (!ent->client->menu) {
+		gi.dprintf("warning:  ent has no menu\n");
+		return;
+	}
+
+	hnd = ent->client->menu;
+
+	if (hnd->cur < 0)
+		return; // no selectable entries
+
+	i = hnd->cur;
+	p = hnd->entries + hnd->cur;
+	do {
+		i++, p++;
+		if (i == hnd->num)
+			i = 0, p = hnd->entries;
+		if (p->SelectFunc)
+			break;
+	} while (i != hnd->cur);
+
+	hnd->cur = i;
+
+	PMenu_Update(ent);
+	gi.unicast (ent, true);
+}
+
+void PMenu_Prev(edict_t *ent)
+{
+	pmenuhnd_t *hnd;
+	int i;
+	pmenu_t *p;
+
+	if (!ent->client->menu) {
+		gi.dprintf("warning:  ent has no menu\n");
+		return;
+	}
+
+	hnd = ent->client->menu;
+
+	if (hnd->cur < 0)
+		return; // no selectable entries
+
+	i = hnd->cur;
+	p = hnd->entries + hnd->cur;
+	do {
+		if (i == 0) {
+			i = hnd->num - 1;
+			p = hnd->entries + i;
+		} else
+			i--, p--;
+		if (p->SelectFunc)
+			break;
+	} while (i != hnd->cur);
+
+	hnd->cur = i;
+
+	PMenu_Update(ent);
+	gi.unicast (ent, true);
+}
+
+void PMenu_Select(edict_t *ent)
+{
+	pmenuhnd_t *hnd;
+	pmenu_t *p;
+
+	if (!ent->client->menu) {
+		gi.dprintf("warning:  ent has no menu\n");
+		return;
+	}
+
+	hnd = ent->client->menu;
+
+	if (hnd->cur < 0)
+		return; // no selectable entries
+
+	p = hnd->entries + hnd->cur;
+
+	if (p->SelectFunc)
+		p->SelectFunc(ent, p);
+}
--- /dev/null
+++ b/crbot/p_trail.c
@@ -1,0 +1,130 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+==============================================================================
+
+PLAYER TRAIL
+
+==============================================================================
+
+This is a circular list containing the a list of points of where
+the player has been recently.  It is used by monsters for pursuit.
+
+.origin		the spot
+.owner		forward link
+.aiment		backward link
+*/
+
+
+#define	TRAIL_LENGTH	8
+
+edict_t		*trail[TRAIL_LENGTH];
+int			trail_head;
+qboolean	trail_active = false;
+
+#define NEXT(n)		(((n) + 1) & (TRAIL_LENGTH - 1))
+#define PREV(n)		(((n) - 1) & (TRAIL_LENGTH - 1))
+
+
+void PlayerTrail_Init (void)
+{
+	int		n;
+
+	if (deathmatch->value /* FIXME || coop */)
+		return;
+
+	for (n = 0; n < TRAIL_LENGTH; n++)
+	{
+		trail[n] = G_Spawn();
+		trail[n]->classname = "player_trail";
+	}
+
+	trail_head = 0;
+	trail_active = true;
+}
+
+
+void PlayerTrail_Add (vec3_t spot)
+{
+	vec3_t	temp;
+
+	if (!trail_active)
+		return;
+
+	VectorCopy (spot, trail[trail_head]->s.origin);
+
+	trail[trail_head]->timestamp = level.time;
+
+	VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
+	trail[trail_head]->s.angles[1] = vectoyaw (temp);
+
+	trail_head = NEXT(trail_head);
+}
+
+
+void PlayerTrail_New (vec3_t spot)
+{
+	if (!trail_active)
+		return;
+
+	PlayerTrail_Init ();
+	PlayerTrail_Add (spot);
+}
+
+
+edict_t *PlayerTrail_PickFirst (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	if (visible(self, trail[marker]))
+	{
+		return trail[marker];
+	}
+
+	if (visible(self, trail[PREV(marker)]))
+	{
+		return trail[PREV(marker)];
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_PickNext (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_LastSpot (void)
+{
+	return trail[PREV(trail_head)];
+}
--- /dev/null
+++ b/crbot/p_view.c
@@ -1,0 +1,1113 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+static	edict_t		*current_player;
+static	gclient_t	*current_client;
+
+static	vec3_t	forward, right, up;
+float	xyspeed;
+
+float	bobmove;
+int		bobcycle;		// odd cycles are right foot going forward
+float	bobfracsin;		// sin(bobfrac*M_PI)
+
+/*
+===============
+SV_CalcRoll
+
+===============
+*/
+float SV_CalcRoll (vec3_t, vec3_t velocity)
+{
+	float	sign;
+	float	side;
+	float	value;
+	
+	side = DotProduct (velocity, right);
+	sign = side < 0 ? -1 : 1;
+	side = fabs(side);
+	
+	value = sv_rollangle->value;
+
+	if (side < sv_rollspeed->value)
+		side = side * value / sv_rollspeed->value;
+	else
+		side = value;
+	
+	return side*sign;
+	
+}
+
+
+/*
+===============
+P_DamageFeedback
+
+Handles color blends and view kicks
+===============
+*/
+void P_DamageFeedback (edict_t *player)
+{
+	gclient_t	*client;
+	float	side;
+	float	realcount, count, kick;
+	vec3_t	v;
+	int		r, l;
+	static	vec3_t	power_color = {0.0, 1.0, 0.0};
+	static	vec3_t	acolor = {1.0, 1.0, 1.0};
+	static	vec3_t	bcolor = {1.0, 0.0, 0.0};
+
+	client = player->client;
+
+	// flash the backgrounds behind the status numbers
+	client->ps.stats[STAT_FLASHES] = 0;
+	if (client->damage_blood)
+		client->ps.stats[STAT_FLASHES] |= 1;
+	if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+		client->ps.stats[STAT_FLASHES] |= 2;
+
+	// total points of damage shot at the player this frame
+	count = (client->damage_blood + client->damage_armor + client->damage_parmor);
+	if (count == 0)
+		return;		// didn't take any damage
+
+	// start a pain animation if still in the player model
+	if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255)
+	{
+		static int		i;
+
+		client->anim_priority = ANIM_PAIN;
+		if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		{
+			player->s.frame = FRAME_crpain1-1;
+			client->anim_end = FRAME_crpain4;
+		}
+		else
+		{
+			i = (i+1)%3;
+			switch (i)
+			{
+			case 0:
+				player->s.frame = FRAME_pain101-1;
+				client->anim_end = FRAME_pain104;
+				break;
+			case 1:
+				player->s.frame = FRAME_pain201-1;
+				client->anim_end = FRAME_pain204;
+				break;
+			case 2:
+				player->s.frame = FRAME_pain301-1;
+				client->anim_end = FRAME_pain304;
+				break;
+			}
+		}
+	}
+
+	realcount = count;
+	if (count < 10)
+		count = 10;	// allways make a visible effect
+
+	// play an apropriate pain sound
+	if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+	{
+		r = 1 + (rand()&1);
+		player->pain_debounce_time = level.time + 0.7;
+		if (player->health < 25)
+			l = 25;
+		else if (player->health < 50)
+			l = 50;
+		else if (player->health < 75)
+			l = 75;
+		else
+			l = 100;
+		gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
+	}
+
+	// the total alpha of the blend is allways proportional to count
+	if (client->damage_alpha < 0)
+		client->damage_alpha = 0;
+	client->damage_alpha += count*0.01;
+	if (client->damage_alpha < 0.2)
+		client->damage_alpha = 0.2;
+	if (client->damage_alpha > 0.6)
+		client->damage_alpha = 0.6;		// don't go too saturated
+
+	// the color of the blend will vary based on how much was absorbed
+	// by different armors
+	VectorClear (v);
+	if (client->damage_parmor)
+		VectorMA (v, (float)client->damage_parmor/realcount, power_color, v);
+	if (client->damage_armor)
+		VectorMA (v, (float)client->damage_armor/realcount,  acolor, v);
+	if (client->damage_blood)
+		VectorMA (v, (float)client->damage_blood/realcount,  bcolor, v);
+	VectorCopy (v, client->damage_blend);
+
+
+	//
+	// calculate view angle kicks
+	//
+	kick = abs(client->damage_knockback);
+	if (kick && player->health > 0)	// kick of 0 means no view adjust at all
+	{
+		kick = kick * 100 / player->health;
+
+		if (kick < count*0.5)
+			kick = count*0.5;
+		if (kick > 50)
+			kick = 50;
+
+		VectorSubtract (client->damage_from, player->s.origin, v);
+		VectorNormalize (v);
+		
+		side = DotProduct (v, right);
+		client->v_dmg_roll = kick*side*0.3;
+		
+		side = -DotProduct (v, forward);
+		client->v_dmg_pitch = kick*side*0.3;
+
+		client->v_dmg_time = level.time + DAMAGE_TIME;
+	}
+
+	//
+	// clear totals
+	//
+	client->damage_blood = 0;
+	client->damage_armor = 0;
+	client->damage_parmor = 0;
+	client->damage_knockback = 0;
+}
+
+
+
+
+/*
+===============
+SV_CalcViewOffset
+
+Auto pitching on slopes?
+
+  fall from 128: 400 = 160000
+  fall from 256: 580 = 336400
+  fall from 384: 720 = 518400
+  fall from 512: 800 = 640000
+  fall from 640: 960 = 
+
+  damage = deltavelocity*deltavelocity  * 0.0001
+
+===============
+*/
+void SV_CalcViewOffset (edict_t *ent)
+{
+	float		*angles;
+	float		bob;
+	float		ratio;
+	float		delta;
+	vec3_t		v;
+
+
+//===================================
+
+	// base angles
+	angles = ent->client->ps.kick_angles;
+
+	// if dead, fix the angle and don't add any kick
+	if (ent->deadflag)
+	{
+		VectorClear (angles);
+
+		ent->client->ps.viewangles[ROLL] = 40;
+		ent->client->ps.viewangles[PITCH] = -15;
+		ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
+	}
+	else
+	{
+		// add angles based on weapon kick
+
+		VectorCopy (ent->client->kick_angles, angles);
+
+		// add angles based on damage kick
+
+		ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
+		if (ratio < 0)
+		{
+			ratio = 0;
+			ent->client->v_dmg_pitch = 0;
+			ent->client->v_dmg_roll = 0;
+		}
+		angles[PITCH] += ratio * ent->client->v_dmg_pitch;
+		angles[ROLL] += ratio * ent->client->v_dmg_roll;
+
+		// add pitch based on fall kick
+
+		ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+		if (ratio < 0)
+			ratio = 0;
+		angles[PITCH] += ratio * ent->client->fall_value;
+
+		// add angles based on velocity
+
+		delta = DotProduct (ent->velocity, forward);
+		angles[PITCH] += delta*run_pitch->value;
+		
+		delta = DotProduct (ent->velocity, right);
+		angles[ROLL] += delta*run_roll->value;
+
+		// add angles based on bob
+
+		delta = bobfracsin * bob_pitch->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		angles[PITCH] += delta;
+		delta = bobfracsin * bob_roll->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		if (bobcycle & 1)
+			delta = -delta;
+		angles[ROLL] += delta;
+	}
+
+//===================================
+
+	// base origin
+
+	VectorClear (v);
+
+	// add view height
+
+	v[2] += ent->viewheight;
+
+	// add fall height
+
+	ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+	if (ratio < 0)
+		ratio = 0;
+	v[2] -= ratio * ent->client->fall_value * 0.4;
+
+	// add bob height
+
+	bob = bobfracsin * xyspeed * bob_up->value;
+	if (bob > 6)
+		bob = 6;
+	//gi.DebugGraph (bob *2, 255);
+	v[2] += bob;
+
+	// add kick offset
+
+	VectorAdd (v, ent->client->kick_origin, v);
+
+	// absolutely bound offsets
+	// so the view can never be outside the player box
+
+	if (v[0] < -14)
+		v[0] = -14;
+	else if (v[0] > 14)
+		v[0] = 14;
+	if (v[1] < -14)
+		v[1] = -14;
+	else if (v[1] > 14)
+		v[1] = 14;
+	if (v[2] < -22)
+		v[2] = -22;
+	else if (v[2] > 30)
+		v[2] = 30;
+
+	VectorCopy (v, ent->client->ps.viewoffset);
+}
+
+/*
+==============
+SV_CalcGunOffset
+==============
+*/
+void SV_CalcGunOffset (edict_t *ent)
+{
+	int		i;
+	float	delta;
+
+	// gun angles from bobbing
+	ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
+	ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
+	if (bobcycle & 1)
+	{
+		ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
+		ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
+	}
+
+	ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
+
+	// gun angles from delta movement
+	for (i=0 ; i<3 ; i++)
+	{
+		delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i];
+		if (delta > 180)
+			delta -= 360;
+		if (delta < -180)
+			delta += 360;
+		if (delta > 45)
+			delta = 45;
+		if (delta < -45)
+			delta = -45;
+		if (i == YAW)
+			ent->client->ps.gunangles[ROLL] += 0.1*delta;
+		ent->client->ps.gunangles[i] += 0.2 * delta;
+	}
+
+	// gun height
+	VectorClear (ent->client->ps.gunoffset);
+//	ent->ps->gunorigin[2] += bob;
+
+	// gun_x / gun_y / gun_z are development tools
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value);
+		ent->client->ps.gunoffset[i] += right[i]*gun_x->value;
+		ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value);
+	}
+}
+
+
+/*
+=============
+SV_AddBlend
+=============
+*/
+void SV_AddBlend (float r, float g, float b, float a, float *v_blend)
+{
+	float	a2, a3;
+
+	if (a <= 0)
+		return;
+	a2 = v_blend[3] + (1-v_blend[3])*a;	// new total alpha
+	a3 = v_blend[3]/a2;		// fraction of color from old
+
+	v_blend[0] = v_blend[0]*a3 + r*(1-a3);
+	v_blend[1] = v_blend[1]*a3 + g*(1-a3);
+	v_blend[2] = v_blend[2]*a3 + b*(1-a3);
+	v_blend[3] = a2;
+}
+
+
+/*
+=============
+SV_CalcBlend
+=============
+*/
+void SV_CalcBlend (edict_t *ent)
+{
+	int		contents;
+	vec3_t	vieworg;
+	int		remaining;
+
+	ent->client->ps.blend[0] = ent->client->ps.blend[1] = 
+		ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
+
+	// add for contents
+	VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg);
+	contents = gi.pointcontents (vieworg);
+	if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) )
+		ent->client->ps.rdflags |= RDF_UNDERWATER;
+	else
+		ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	if (contents & (CONTENTS_SOLID|CONTENTS_LAVA))
+		SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_SLIME)
+		SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_WATER)
+		SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
+
+	// add for powerups
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		remaining = ent->client->enviro_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		remaining = ent->client->breather_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend);
+	}
+
+	// add for damage
+	if (ent->client->damage_alpha > 0)
+		SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1]
+		,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend);
+
+	if (ent->client->bonus_alpha > 0)
+		SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend);
+
+	// drop the damage value
+	ent->client->damage_alpha -= 0.06;
+	if (ent->client->damage_alpha < 0)
+		ent->client->damage_alpha = 0;
+
+	// drop the bonus value
+	ent->client->bonus_alpha -= 0.1;
+	if (ent->client->bonus_alpha < 0)
+		ent->client->bonus_alpha = 0;
+}
+
+
+/*
+=================
+P_FallingDamage
+=================
+*/
+void P_FallingDamage (edict_t *ent)
+{
+	float	delta;
+	int		damage;
+	vec3_t	dir;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+ 	if (ent->movetype == MOVETYPE_NOCLIP)
+		return;
+
+	if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity))
+	{
+		delta = ent->client->oldvelocity[2];
+	}
+	else
+	{
+		if (!ent->groundentity)
+			return;
+		delta = ent->velocity[2] - ent->client->oldvelocity[2];
+	}
+	delta = delta*delta * 0.0001;
+
+//ZOID
+	// never take damage if just release grapple or on grapple
+	if (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2 ||
+		(ent->client->ctf_grapple && 
+		ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY))
+		return;
+//ZOID
+
+	// never take falling damage if completely underwater
+	if (ent->waterlevel == 3)
+		return;
+	if (ent->waterlevel == 2)
+		delta *= 0.25;
+	if (ent->waterlevel == 1)
+		delta *= 0.5;
+
+	if (delta < 1)
+		return;
+
+	if (delta < 15)
+	{
+		ent->s.event = EV_FOOTSTEP;
+		return;
+	}
+
+	ent->client->fall_value = delta*0.5;
+	if (ent->client->fall_value > 40)
+		ent->client->fall_value = 40;
+	ent->client->fall_time = level.time + FALL_TIME;
+
+	if (delta > 30)
+	{
+		if (ent->health > 0)
+		{
+			if (delta >= 55)
+				ent->s.event = EV_FALLFAR;
+			else
+				ent->s.event = EV_FALL;
+		}
+		ent->pain_debounce_time = level.time;	// no normal pain sound
+		damage = (delta-30)/2;
+		if (damage < 1)
+			damage = 1;
+		VectorSet (dir, 0, 0, 1);
+
+		if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) )
+			T_Damage (ent, WORLD, WORLD, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING);
+	}
+	else
+	{
+		ent->s.event = EV_FALLSHORT;
+		return;
+	}
+}
+
+
+
+/*
+=============
+P_WorldEffects
+=============
+*/
+void P_WorldEffects (void)
+{
+	qboolean	breather;
+	qboolean	envirosuit;
+	int			waterlevel, old_waterlevel;
+
+	if (current_player->movetype == MOVETYPE_NOCLIP)
+	{
+		current_player->air_finished = level.time + 12;	// don't need air
+		return;
+	}
+
+	waterlevel = current_player->waterlevel;
+	old_waterlevel = current_client->old_waterlevel;
+	current_client->old_waterlevel = waterlevel;
+
+	breather = current_client->breather_framenum > level.framenum;
+	envirosuit = current_client->enviro_framenum > level.framenum;
+
+	//
+	// if just entered a water volume, play a sound
+	//
+	if (!old_waterlevel && waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		if (current_player->watertype & CONTENTS_LAVA)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_SLIME)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_WATER)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		current_player->flags |= FL_INWATER;
+
+		// clear damage_debounce, so the pain sound will play immediately
+		current_player->damage_debounce_time = level.time - 1;
+	}
+
+	//
+	// if just completely exited a water volume, play a sound
+	//
+	if (old_waterlevel && ! waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+		current_player->flags &= ~FL_INWATER;
+	}
+
+	//
+	// check for head just going under water
+	//
+	if (old_waterlevel != 3 && waterlevel == 3)
+	{
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
+	}
+
+	//
+	// check for head just coming out of water
+	//
+	if (old_waterlevel == 3 && waterlevel != 3)
+	{
+		if (current_player->air_finished < level.time)
+		{	// gasp for air
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
+			PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		}
+		else  if (current_player->air_finished < level.time + 11)
+		{	// just break surface
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
+		}
+	}
+
+	//
+	// check for drowning
+	//
+	if (waterlevel == 3)
+	{
+		// breather or envirosuit give air
+		if (breather || envirosuit)
+		{
+			current_player->air_finished = level.time + 10;
+
+			if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
+			{
+				if (!current_client->breather_sound)
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
+				current_client->breather_sound ^= 1;
+				PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+				//FIXME: release a bubble?
+			}
+		}
+
+		// if out of air, start drowning
+		if (current_player->air_finished < level.time)
+		{	// drown!
+			if (current_player->client->next_drown_time < level.time 
+				&& current_player->health > 0)
+			{
+				current_player->client->next_drown_time = level.time + 1;
+
+				// take more damage the longer underwater
+				current_player->dmg += 2;
+				if (current_player->dmg > 15)
+					current_player->dmg = 15;
+
+				// play a gurp sound instead of a normal pain sound
+				if (current_player->health <= current_player->dmg)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
+				else if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
+
+				current_player->pain_debounce_time = level.time;
+
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+			}
+		}
+	}
+	else
+	{
+		current_player->air_finished = level.time + 12;
+		current_player->dmg = 2;
+	}
+
+	//
+	// check for sizzle damage
+	//
+	if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+	{
+		if (current_player->watertype & CONTENTS_LAVA)
+		{
+			if (current_player->health > 0
+				&& current_player->pain_debounce_time <= level.time
+				&& current_client->invincible_framenum < level.framenum)
+			{
+				if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
+				current_player->pain_debounce_time = level.time + 1;
+			}
+
+			if (envirosuit)	// take 1/3 damage with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA);
+			else
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA);
+		}
+
+		if (current_player->watertype & CONTENTS_SLIME)
+		{
+			if (!envirosuit)
+			{	// no damage from slime with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME);
+			}
+		}
+	}
+}
+
+
+/*
+===============
+G_SetClientEffects
+===============
+*/
+void G_SetClientEffects (edict_t *ent)
+{
+	int		pa_type;
+	int		remaining;
+
+	ent->s.effects = 0;
+	ent->s.renderfx = 0;
+
+	if (ent->health <= 0 || level.intermissiontime)
+		return;
+
+	if (ent->powerarmor_time > level.time)
+	{
+		pa_type = PowerArmorType (ent);
+		if (pa_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (pa_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+
+//ZOID
+	CTFEffects(ent);
+//ZOID
+
+	if (ent->client->quad_framenum > level.framenum
+//ZOID
+		&& (level.framenum & 8)
+//ZOID
+		)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_QUAD;
+	}
+
+	if (ent->client->invincible_framenum > level.framenum
+//ZOID
+		&& (level.framenum & 8)
+//ZOID
+		)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_PENT;
+	}
+
+	// show cheaters!!!
+	if (ent->flags & FL_GODMODE)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
+	}
+}
+
+
+/*
+===============
+G_SetClientEvent
+===============
+*/
+void G_SetClientEvent (edict_t *ent)
+{
+	if (ent->s.event)
+		return;
+
+	if ( ent->groundentity && xyspeed > 225)
+	{
+		if ( (int)(current_client->bobtime+bobmove) != bobcycle )
+			ent->s.event = EV_FOOTSTEP;
+	}
+}
+
+/*
+===============
+G_SetClientSound
+===============
+*/
+void G_SetClientSound (edict_t *ent)
+{
+	char	*weap;
+
+	if (ent->client->resp.game_helpchanged != game.helpchanged)
+	{
+		ent->client->resp.game_helpchanged = game.helpchanged;
+		ent->client->resp.helpchanged = 1;
+	}
+
+	// help beep (no more than three times)
+	if (ent->client->resp.helpchanged && ent->client->resp.helpchanged <= 3 && !(level.framenum&63) )
+	{
+		ent->client->resp.helpchanged++;
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
+	}
+
+
+	if (ent->client->pers.weapon)
+		weap = ent->client->pers.weapon->classname;
+	else
+		weap = "";
+
+	if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+		ent->s.sound = snd_fry;
+	else if (strcmp(weap, "weapon_railgun") == 0)
+		ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
+	else if (strcmp(weap, "weapon_bfg") == 0)
+		ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
+	else if (ent->client->weapon_sound)
+		ent->s.sound = ent->client->weapon_sound;
+	else
+		ent->s.sound = 0;
+}
+
+/*
+===============
+G_SetClientFrame
+===============
+*/
+void G_SetClientFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	qboolean	duck, run;
+
+    if (ent->bot_info) return;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+	client = ent->client;
+
+	if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		duck = true;
+	else
+		duck = false;
+	if (xyspeed)
+		run = true;
+	else
+		run = false;
+
+	// check for stand/duck and stop/go transitions
+	if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH)
+		goto newanim;
+	if (run != client->anim_run && client->anim_priority == ANIM_BASIC)
+		goto newanim;
+	if (!ent->groundentity && client->anim_priority <= ANIM_WAVE)
+		goto newanim;
+
+	if (ent->s.frame < client->anim_end)
+	{	// continue an animation
+		ent->s.frame++;
+		return;
+	}
+
+	if (client->anim_priority == ANIM_DEATH)
+		return;		// stay there
+	if (client->anim_priority == ANIM_JUMP)
+	{
+		if (!ent->groundentity)
+			return;		// stay there
+		ent->client->anim_priority = ANIM_WAVE;
+		ent->s.frame = FRAME_jump3;
+		ent->client->anim_end = FRAME_jump6;
+		return;
+	}
+
+newanim:
+	// return to either a running or standing frame
+	client->anim_priority = ANIM_BASIC;
+	client->anim_duck = duck;
+	client->anim_run = run;
+
+	if (!ent->groundentity)
+	{
+//ZOID: if on grapple, don't go into jump frame, go into standing
+//frame
+		if (client->ctf_grapple) {
+			ent->s.frame = FRAME_stand01;
+			client->anim_end = FRAME_stand40;
+		} else {
+//ZOID
+		client->anim_priority = ANIM_JUMP;
+		if (ent->s.frame != FRAME_jump2)
+			ent->s.frame = FRAME_jump1;
+		client->anim_end = FRAME_jump2;
+	}
+	}
+	else if (run)
+	{	// running
+		if (duck)
+		{
+			ent->s.frame = FRAME_crwalk1;
+			client->anim_end = FRAME_crwalk6;
+		}
+		else
+		{
+			ent->s.frame = FRAME_run1;
+			client->anim_end = FRAME_run6;
+		}
+	}
+	else
+	{	// standing
+		if (duck)
+		{
+			ent->s.frame = FRAME_crstnd01;
+			client->anim_end = FRAME_crstnd19;
+		}
+		else
+		{
+			ent->s.frame = FRAME_stand01;
+			client->anim_end = FRAME_stand40;
+		}
+	}
+}
+
+
+/*
+=================
+ClientEndServerFrame
+
+Called for each player at the end of the server frame
+and right after spawning
+=================
+*/
+void ClientEndServerFrame (edict_t *ent)
+{
+	float	bobtime;
+	int		i;
+
+	current_player = ent;
+	current_client = ent->client;
+
+    if (ctf->value) {
+      current_client->pers.team_no = (current_client->resp.ctf_team==CTF_TEAM1) ? 1 : 2;
+      }
+
+	//
+	// If the origin or velocity have changed since ClientThink(),
+	// update the pmove values.  This will happen when the client
+	// is pushed by a bmodel or kicked by an explosion.
+	// 
+	// If it wasn't updated here, the view position would lag a frame
+	// behind the body position when pushed -- "sinking into plats"
+	//
+	for (i=0 ; i<3 ; i++)
+	{
+		current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0;
+		current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0;
+	}
+
+	//
+	// If the end of unit layout is displayed, don't give
+	// the player any normal movement attributes
+	//
+	if (level.intermissiontime)
+	{
+		// FIXME: add view drifting here?
+		current_client->ps.blend[3] = 0;
+		current_client->ps.fov = 90;
+		G_SetStats (ent);
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+	// burn from lava, etc
+	P_WorldEffects ();
+
+	//
+	// set model angles from view angles so other things in
+	// the world can tell which direction you are looking
+	//
+	if (ent->client->v_angle[PITCH] > 180)
+		ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3;
+	else
+		ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3;
+	ent->s.angles[YAW] = ent->client->v_angle[YAW];
+	ent->s.angles[ROLL] = 0;
+	ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4;
+
+	//
+	// calculate speed and cycle to be used for
+	// all cyclic walking effects
+	//
+	xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);
+
+	if (xyspeed < 5)
+	{
+		bobmove = 0;
+		current_client->bobtime = 0;	// start at beginning of cycle again
+	}
+	else if (ent->groundentity)
+	{	// so bobbing only cycles when on ground
+		if (xyspeed > 210)
+			bobmove = 0.25;
+		else if (xyspeed > 100)
+			bobmove = 0.125;
+		else
+			bobmove = 0.0625;
+	}
+	
+	bobtime = (current_client->bobtime += bobmove);
+
+	if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
+		bobtime *= 4;
+
+	bobcycle = (int)bobtime;
+	bobfracsin = fabs(sin(bobtime*M_PI));
+
+	// detect hitting the floor
+	P_FallingDamage (ent);
+
+	// apply all the damage taken this frame
+	P_DamageFeedback (ent);
+
+	// determine the view offsets
+	SV_CalcViewOffset (ent);
+
+	// determine the gun offsets
+	SV_CalcGunOffset (ent);
+
+	// determine the full screen color blend
+	// must be after viewoffset, so eye contents can be
+	// accurately determined
+	// FIXME: with client prediction, the contents
+	// should be determined by the client
+	SV_CalcBlend (ent);
+
+//ZOID
+	if (!ent->client->chase_target)
+//ZOID
+		G_SetStats (ent);
+
+//ZOID
+//update chasecam follower stats
+	for (i = 1; i <= maxclients->value; i++) {
+		edict_t *e = g_edicts + i;
+		if (!ent->inuse ||
+			e->client->chase_target != ent)
+			continue;
+		memcpy(e->client->ps.stats, 
+			ent->client->ps.stats, 
+			sizeof(ent->client->ps.stats));
+		e->client->ps.stats[STAT_LAYOUTS] = 1;
+		break;
+	}
+//ZOID
+
+
+	G_SetClientEvent (ent);
+
+	G_SetClientEffects (ent);
+
+	G_SetClientSound (ent);
+
+	G_SetClientFrame (ent);
+
+	VectorCopy (ent->velocity, ent->client->oldvelocity);
+	VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles);
+
+	// clear weapon kicks
+	VectorClear (ent->client->kick_origin);
+	VectorClear (ent->client->kick_angles);
+
+	// if the scoreboard is up, update it
+	if (ent->client->showscores && !(level.framenum & 31) )
+	{
+//ZOID
+		if (ent->client->menu) {
+			PMenu_Update(ent);
+		} else
+//ZOID
+			DeathmatchScoreboardMessage (ent, ent->enemy);
+		gi.unicast (ent, false);
+	}
+}
+
--- /dev/null
+++ b/crbot/p_weapon.c
@@ -1,0 +1,1354 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+static qboolean	is_quad;
+static byte		is_silenced;
+
+
+void weapon_grenade_fire (edict_t *ent, qboolean held);
+
+
+void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	vec3_t	_distance;
+
+	VectorCopy (distance, _distance);
+	if (client->pers.hand == LEFT_HANDED)
+		_distance[1] *= -1;
+	else if (client->pers.hand == CENTER_HANDED)
+		_distance[1] = 0;
+	G_ProjectSource (point, _distance, forward, right, result);
+}
+
+
+/*
+===============
+PlayerNoise
+
+Each player can have two noise objects associated with it:
+a personal noise (jumping, pain, weapon firing), and a weapon
+target noise (bullet wall impacts)
+
+Monsters that don't directly see the player can move
+to a noise in hopes of seeing the player from there.
+===============
+*/
+void PlayerNoise(edict_t *who, vec3_t where, int type)
+{
+	edict_t		*noise;
+
+	if (type == PNOISE_WEAPON)
+	{
+		if (who->client->silencer_shots)
+		{
+			who->client->silencer_shots--;
+			return;
+		}
+	}
+
+	if (deathmatch->value)
+		return;
+
+	if (who->flags & FL_NOTARGET)
+		return;
+
+
+	if (!who->mynoise)
+	{
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise = noise;
+
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise2 = noise;
+	}
+
+	if (type == PNOISE_SELF || type == PNOISE_WEAPON)
+	{
+		noise = who->mynoise;
+		level.sound_entity = noise;
+		level.sound_entity_framenum = level.framenum;
+	}
+	else // type == PNOISE_IMPACT
+	{
+		noise = who->mynoise2;
+		level.sound2_entity = noise;
+		level.sound2_entity_framenum = level.framenum;
+	}
+
+	VectorCopy (where, noise->s.origin);
+	VectorSubtract (where, noise->maxs, noise->absmin);
+	VectorAdd (where, noise->maxs, noise->absmax);
+	noise->teleport_time = level.time;
+	gi.linkentity (noise);
+}
+
+
+qboolean Pickup_Weapon (edict_t *ent, edict_t *other)
+{
+	int			index;
+	gitem_t		*ammo;
+
+	index = ITEM_INDEX(ent->item);
+
+	if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) 
+		&& other->client->pers.inventory[index])
+	{
+		if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) )
+			return false;	// leave the weapon for others to pickup
+	}
+
+	other->client->pers.inventory[index]++;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) )
+	{
+		// give them some ammo with it
+		ammo = FindItem (ent->item->ammo);
+		if ( (int)dmflags->value & DF_INFINITE_AMMO )
+			Add_Ammo (other, ammo, 1000);
+		else
+			Add_Ammo (other, ammo, ammo->quantity);
+
+		if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) )
+		{
+			if (deathmatch->value)
+			{
+				if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+					ent->flags |= FL_RESPAWN;
+				else
+					SetRespawn (ent, 30);
+			}
+			if (coop->value)
+				ent->flags |= FL_RESPAWN;
+		}
+	}
+
+	if (other->client->pers.weapon != ent->item && 
+		(other->client->pers.inventory[index] == 1) &&
+		( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) )
+		other->client->newweapon = ent->item;
+
+	return true;
+}
+
+
+/*
+===============
+ChangeWeapon
+
+The old weapon has been dropped all the way, so make the new one
+current
+===============
+*/
+void ChangeWeapon (edict_t *ent)
+{
+	int i;
+
+	if (ent->client->grenade_time)
+	{
+		ent->client->grenade_time = level.time;
+		ent->client->weapon_sound = 0;
+		weapon_grenade_fire (ent, false);
+		ent->client->grenade_time = 0;
+	}
+
+	ent->client->pers.lastweapon = ent->client->pers.weapon;
+	ent->client->pers.weapon = ent->client->newweapon;
+	ent->client->newweapon = NULL;
+	ent->client->machinegun_shots = 0;
+
+	// set visible model
+	if (ent->s.modelindex == 255) {
+		if (ent->client->pers.weapon)
+			i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
+		else
+			i = 0;
+		ent->s.skinnum = (ent - g_edicts - 1) | i;
+	}
+
+	if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
+		ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
+	else
+		ent->client->ammo_index = 0;
+
+	if (!ent->client->pers.weapon)
+	{	// dead
+		ent->client->ps.gunindex = 0;
+		return;
+	}
+
+	ent->client->weaponstate = WEAPON_ACTIVATING;
+	ent->client->ps.gunframe = 0;
+	ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
+
+	ent->client->anim_priority = ANIM_PAIN;
+	if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+			ent->s.frame = FRAME_crpain1;
+			ent->client->anim_end = FRAME_crpain4;
+	}
+	else
+	{
+			ent->s.frame = FRAME_pain301;
+			ent->client->anim_end = FRAME_pain304;
+			
+	}
+}
+
+/*
+=================
+NoAmmoWeaponChange
+=================
+*/
+void NoAmmoWeaponChange (edict_t *ent)
+{
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] )
+	{
+		ent->client->newweapon = FindItem ("railgun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] )
+	{
+		ent->client->newweapon = FindItem ("hyperblaster");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] )
+	{
+		ent->client->newweapon = FindItem ("chaingun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] )
+	{
+		ent->client->newweapon = FindItem ("machinegun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("super shotgun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("shotgun");
+		return;
+	}
+	ent->client->newweapon = FindItem ("blaster");
+}
+
+/*
+=================
+Think_Weapon
+
+Called by ClientBeginServerFrame and ClientThink
+=================
+*/
+void Think_Weapon (edict_t *ent)
+{
+	// if just died, put the weapon away
+	if (ent->health < 1)
+	{
+		ent->client->newweapon = NULL;
+		ChangeWeapon (ent);
+	}
+
+	// call active weapon think routine
+	if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
+	{
+		is_quad = (ent->client->quad_framenum > level.framenum);
+		if (ent->client->silencer_shots)
+			is_silenced = MZ_SILENCED;
+		else
+			is_silenced = 0;
+		ent->client->pers.weapon->weaponthink (ent);
+	}
+}
+
+
+/*
+================
+Use_Weapon
+
+Make the weapon ready if there is ammo
+================
+*/
+void Use_Weapon (edict_t *ent, gitem_t *item)
+{
+	int			ammo_index;
+	gitem_t		*ammo_item;
+
+	// see if we're already using it
+	if (item == ent->client->pers.weapon)
+		return;
+
+	if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO))
+	{
+		ammo_item = FindItem(item->ammo);
+		ammo_index = ITEM_INDEX(ammo_item);
+
+		if (!ent->client->pers.inventory[ammo_index])
+		{
+          if (!ent->bot_info) 
+			gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+		  return;
+		}
+
+		if (ent->client->pers.inventory[ammo_index] < item->quantity)
+		{
+          if (!ent->bot_info) 
+			gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+		  return;
+		}
+	}
+
+	// change to this weapon when down
+	ent->client->newweapon = item;
+}
+
+
+
+/*
+================
+Drop_Weapon
+================
+*/
+void Drop_Weapon (edict_t *ent, gitem_t *item)
+{
+	int		index;
+
+	if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+		return;
+
+	index = ITEM_INDEX(item);
+	// see if we're already using it
+	if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) )
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n");
+		return;
+	}
+
+	Drop_Item (ent, item);
+	ent->client->pers.inventory[index]--;
+}
+
+
+/*
+================
+Weapon_Generic
+
+A generic function to handle the basics of weapon thinking
+================
+*/
+#define FRAME_FIRE_FIRST		(FRAME_ACTIVATE_LAST + 1)
+#define FRAME_IDLE_FIRST		(FRAME_FIRE_LAST + 1)
+#define FRAME_DEACTIVATE_FIRST	(FRAME_IDLE_LAST + 1)
+
+static void Weapon_Generic2 (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+{
+	int		n;
+
+	if (ent->client->weaponstate == WEAPON_DROPPING)
+	{
+		if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
+		{
+			ChangeWeapon (ent);
+			return;
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
+		{
+			ent->client->weaponstate = WEAPON_READY;
+			ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+			return;
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
+	{
+		ent->client->weaponstate = WEAPON_DROPPING;
+		ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if ((!ent->client->ammo_index) || 
+				( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
+			{
+				ent->client->ps.gunframe = FRAME_FIRE_FIRST;
+				ent->client->weaponstate = WEAPON_FIRING;
+
+				// start the animation
+				ent->client->anim_priority = ANIM_ATTACK;
+				if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+				{
+					ent->s.frame = FRAME_crattak1-1;
+					ent->client->anim_end = FRAME_crattak9;
+				}
+				else
+				{
+					ent->s.frame = FRAME_attack1-1;
+					ent->client->anim_end = FRAME_attack8;
+				}
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+		}
+		else
+		{
+			if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+			{
+				ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+				return;
+			}
+
+			if (pause_frames)
+			{
+				for (n = 0; pause_frames[n]; n++)
+				{
+					if (ent->client->ps.gunframe == pause_frames[n])
+					{
+						if (rand()&15)
+							return;
+					}
+				}
+			}
+
+			ent->client->ps.gunframe++;
+			return;
+		}
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		for (n = 0; fire_frames[n]; n++)
+		{
+			if (ent->client->ps.gunframe == fire_frames[n])
+			{
+//ZOID
+				if (!CTFApplyStrengthSound(ent))
+//ZOID
+				if (ent->client->quad_framenum > level.framenum)
+					gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+//ZOID
+				CTFApplyHasteSound(ent);
+//ZOID
+
+				fire (ent);
+				break;
+			}
+		}
+
+		if (!fire_frames[n])
+			ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
+			ent->client->weaponstate = WEAPON_READY;
+	}
+}
+
+//ZOID
+void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+{
+	int oldstate = ent->client->weaponstate;
+
+	Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, 
+		FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, 
+		fire_frames, fire);
+
+	// run the weapon frame again if hasted
+	if (cistrcmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 &&
+		ent->client->weaponstate == WEAPON_FIRING)
+		return;
+
+	if ((CTFApplyHaste(ent) ||
+		(cistrcmp(ent->client->pers.weapon->pickup_name, "Grapple") == 0 &&
+		ent->client->weaponstate != WEAPON_FIRING))
+		&& oldstate == ent->client->weaponstate) {
+		Weapon_Generic2 (ent, FRAME_ACTIVATE_LAST, FRAME_FIRE_LAST, 
+			FRAME_IDLE_LAST, FRAME_DEACTIVATE_LAST, pause_frames, 
+			fire_frames, fire);
+	}
+}
+//ZOID
+
+/*
+======================================================================
+
+GRENADE
+
+======================================================================
+*/
+
+#define GRENADE_TIMER		3.0
+#define GRENADE_MINSPEED	400
+#define GRENADE_MAXSPEED	800
+
+void weapon_grenade_fire (edict_t *ent, qboolean held)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+	int		damage = 125;
+	float	timer;
+	int		speed;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+		damage *= 4;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	timer = ent->client->grenade_time - level.time;
+	speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+	fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->grenade_time = level.time + 1.0;
+}
+
+void Weapon_Grenade (edict_t *ent)
+{
+	if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+	{
+		ChangeWeapon (ent);
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		ent->client->weaponstate = WEAPON_READY;
+		ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if (ent->client->pers.inventory[ent->client->ammo_index])
+			{
+				ent->client->ps.gunframe = 1;
+				ent->client->weaponstate = WEAPON_FIRING;
+				ent->client->grenade_time = 0;
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+			return;
+		}
+
+		if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48))
+		{
+			if (rand()&15)
+				return;
+		}
+
+		if (++ent->client->ps.gunframe > 48)
+			ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->ps.gunframe == 5)
+			gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+
+		if (ent->client->ps.gunframe == 11)
+		{
+			if (!ent->client->grenade_time)
+			{
+				ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+				ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+			}
+
+			// they waited too long, detonate it in their hand
+			if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
+			{
+				ent->client->weapon_sound = 0;
+				weapon_grenade_fire (ent, true);
+				ent->client->grenade_blew_up = true;
+			}
+
+			if (ent->client->buttons & BUTTON_ATTACK)
+				return;
+
+			if (ent->client->grenade_blew_up)
+			{
+				if (level.time >= ent->client->grenade_time)
+				{
+					ent->client->ps.gunframe = 15;
+					ent->client->grenade_blew_up = false;
+				}
+				else
+				{
+					return;
+				}
+			}
+		}
+
+		if (ent->client->ps.gunframe == 12)
+		{
+			ent->client->weapon_sound = 0;
+			weapon_grenade_fire (ent, false);
+		}
+
+		if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time))
+			return;
+
+		ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == 16)
+		{
+			ent->client->grenade_time = 0;
+			ent->client->weaponstate = WEAPON_READY;
+		}
+	}
+}
+
+/*
+======================================================================
+
+GRENADE LAUNCHER
+
+======================================================================
+*/
+
+void weapon_grenadelauncher_fire (edict_t *ent)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+	int		damage = 120;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+		damage *= 4;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_GRENADE | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_GrenadeLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {34, 51, 59, 0};
+	static int	fire_frames[]	= {6, 0};
+
+	Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
+}
+
+/*
+======================================================================
+
+ROCKET
+
+======================================================================
+*/
+
+void Weapon_RocketLauncher_Fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius;
+	int		radius_damage;
+
+	damage = 100 + (int)(qrandom() * 20.0);
+	radius_damage = 120;
+	damage_radius = 120;
+	if (is_quad)
+	{
+		damage *= 4;
+		radius_damage *= 4;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_ROCKET | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_RocketLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {25, 33, 42, 50, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
+}
+
+
+/*
+======================================================================
+
+BLASTER / HYPERBLASTER
+
+======================================================================
+*/
+
+void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	offset;
+
+	if (is_quad)
+		damage *= 4;
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	VectorSet(offset, 24, 8, ent->viewheight-8);
+	VectorAdd (offset, g_offset, offset);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	fire_blaster (ent, start, forward, damage, 1000, effect, hyper);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	if (hyper)
+		gi.WriteByte (MZ_HYPERBLASTER | is_silenced);
+	else
+		gi.WriteByte (MZ_BLASTER | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+
+void Weapon_Blaster_Fire (edict_t *ent)
+{
+	int		damage;
+
+	if (deathmatch->value)
+		damage = 15;
+	else
+		damage = 10;
+	Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
+	ent->client->ps.gunframe++;
+}
+
+void Weapon_Blaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {19, 32, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
+}
+
+
+void Weapon_HyperBlaster_Fire (edict_t *ent)
+{
+	float	rotation;
+	vec3_t	offset;
+	int		effect;
+	int		damage;
+
+	ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe++;
+	}
+	else
+	{
+		if (! ent->client->pers.inventory[ent->client->ammo_index] )
+		{
+			if (level.time >= ent->pain_debounce_time)
+			{
+				gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+				ent->pain_debounce_time = level.time + 1;
+			}
+			NoAmmoWeaponChange (ent);
+		}
+		else
+		{
+			rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6;
+			offset[0] = -4 * sin(rotation);
+			offset[1] = 0;
+			offset[2] = 4 * cos(rotation);
+
+			if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9))
+				effect = EF_HYPERBLASTER;
+			else
+				effect = 0;
+			if (deathmatch->value)
+				damage = 15;
+			else
+				damage = 20;
+			Blaster_Fire (ent, offset, damage, true, effect);
+			if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+				ent->client->pers.inventory[ent->client->ammo_index]--;
+		}
+
+		ent->client->ps.gunframe++;
+		if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index])
+			ent->client->ps.gunframe = 6;
+	}
+
+	if (ent->client->ps.gunframe == 12)
+	{
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+		ent->client->weapon_sound = 0;
+	}
+
+}
+
+void Weapon_HyperBlaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {0};
+	static int	fire_frames[]	= {6, 7, 8, 9, 10, 11, 0};
+
+	Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
+}
+
+/*
+======================================================================
+
+MACHINEGUN / CHAINGUN
+
+======================================================================
+*/
+
+void Machinegun_Fire (edict_t *ent)
+{
+	int	i;
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		angles;
+	int			damage = 8;
+	int			kick = 2;
+	vec3_t		offset;
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->machinegun_shots = 0;
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->ps.gunframe == 5)
+		ent->client->ps.gunframe = 4;
+	else
+		ent->client->ps.gunframe = 5;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
+	{
+		ent->client->ps.gunframe = 6;
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	for (i=1 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+	ent->client->kick_origin[0] = crandom() * 0.35;
+	ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;
+
+	// raise the gun as it is firing
+	if (!deathmatch->value)
+	{
+		ent->client->machinegun_shots++;
+		if (ent->client->machinegun_shots > 9)
+			ent->client->machinegun_shots = 9;
+	}
+
+	// get start / end positions
+	VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
+	AngleVectors (angles, forward, right, NULL);
+	VectorSet(offset, 0, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_MACHINEGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_Machinegun (edict_t *ent)
+{
+	static int	pause_frames[]	= {23, 45, 0};
+	static int	fire_frames[]	= {4, 5, 0};
+
+	Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire);
+}
+
+void Chaingun_Fire (edict_t *ent)
+{
+	int			i;
+	int			shots;
+	vec3_t		start;
+	vec3_t		forward, right, up;
+	float		r, u;
+	vec3_t		offset;
+	int			damage;
+	int			kick = 2;
+
+	if (deathmatch->value)
+		damage = 6;
+	else
+		damage = 8;
+
+	if (ent->client->ps.gunframe == 5)
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0);
+
+	if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe = 32;
+		ent->client->weapon_sound = 0;
+		return;
+	}
+	else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK)
+		&& ent->client->pers.inventory[ent->client->ammo_index])
+	{
+		ent->client->ps.gunframe = 15;
+	}
+	else
+	{
+		ent->client->ps.gunframe++;
+	}
+
+	if (ent->client->ps.gunframe == 22)
+	{
+		ent->client->weapon_sound = 0;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0);
+	}
+	else
+	{
+		ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
+	}
+
+	if (ent->client->ps.gunframe <= 9)
+		shots = 1;
+	else if (ent->client->ps.gunframe <= 14)
+	{
+		if (ent->client->buttons & BUTTON_ATTACK)
+			shots = 2;
+		else
+			shots = 1;
+	}
+	else
+		shots = 3;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < shots)
+		shots = ent->client->pers.inventory[ent->client->ammo_index];
+
+	if (!shots)
+	{
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+
+	for (i=0 ; i<shots ; i++)
+	{
+		// get start / end positions
+		AngleVectors (ent->client->v_angle, forward, right, up);
+		r = 7 + crandom()*4;
+		u = crandom()*4;
+		VectorSet(offset, 0, r, u + ent->viewheight-8);
+		P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+		fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
+	}
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= shots;
+}
+
+
+void Weapon_Chaingun (edict_t *ent)
+{
+	static int	pause_frames[]	= {38, 43, 51, 61, 0};
+	static int	fire_frames[]	= {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+
+	Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire);
+}
+
+
+/*
+======================================================================
+
+SHOTGUN / SUPERSHOTGUN
+
+======================================================================
+*/
+
+void weapon_shotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage = 4;
+	int			kick = 8;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	if (deathmatch->value)
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
+	else
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_Shotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {22, 28, 34, 0};
+	static int	fire_frames[]	= {8, 9, 0};
+
+	Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire);
+}
+
+
+void weapon_supershotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	vec3_t		v;
+	int			damage = 6;
+	int			kick = 12;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	v[PITCH] = ent->client->v_angle[PITCH];
+	v[YAW]   = ent->client->v_angle[YAW] - 5;
+	v[ROLL]  = ent->client->v_angle[ROLL];
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+	v[YAW]   = ent->client->v_angle[YAW] + 5;
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SSHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 2;
+}
+
+void Weapon_SuperShotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {29, 42, 57, 0};
+	static int	fire_frames[]	= {7, 0};
+
+	Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
+}
+
+
+
+/*
+======================================================================
+
+RAILGUN
+
+======================================================================
+*/
+
+void weapon_railgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage;
+	int			kick;
+
+	if (deathmatch->value)
+	{	// normal damage is too extreme in dm
+		damage = 100;
+		kick = 200;
+	}
+	else
+	{
+		damage = 150;
+		kick = 250;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -3, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -3;
+
+	VectorSet(offset, 0, 7,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rail (ent, start, forward, damage, kick);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_RAILGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+
+void Weapon_Railgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {56, 0};
+	static int	fire_frames[]	= {4, 0};
+
+	Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire);
+}
+
+
+/*
+======================================================================
+
+BFG10K
+
+======================================================================
+*/
+
+void weapon_bfg_fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius = 1000;
+
+	if (deathmatch->value)
+		damage = 200;
+	else
+		damage = 500;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		// send muzzle flash
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_BFG | is_silenced);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		ent->client->ps.gunframe++;
+
+		PlayerNoise(ent, start, PNOISE_WEAPON);
+		return;
+	}
+
+	// cells can go down during windup (from power armor hits), so
+	// check again and abort firing if we don't have enough now
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 50)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (is_quad)
+		damage *= 4;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+
+	// make a big pitch kick with an inverse fall
+	ent->client->v_dmg_pitch = -40;
+	ent->client->v_dmg_roll = crandom()*8;
+	ent->client->v_dmg_time = level.time + DAMAGE_TIME;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bfg (ent, start, forward, damage, 400, damage_radius);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 50;
+}
+
+void Weapon_BFG (edict_t *ent)
+{
+	static int	pause_frames[]	= {39, 45, 50, 55, 0};
+	static int	fire_frames[]	= {9, 17, 0};
+
+	Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire);
+}
+
+
+//======================================================================
--- /dev/null
+++ b/crbot/q_shared.c
@@ -1,0 +1,1075 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define DEG2RAD( a ) ( a * M_PI ) / 180.0F
+
+vec3_t vec3_origin = {0,0,0};
+
+//============================================================================
+
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees )
+{
+	float	m[3][3];
+	float	im[3][3];
+	float	zrot[3][3];
+	float	tmpmat[3][3];
+	float	rot[3][3];
+	int	i;
+	vec3_t vr, vup, vf;
+
+	vf[0] = dir[0];
+	vf[1] = dir[1];
+	vf[2] = dir[2];
+
+	PerpendicularVector( vr, dir );
+	CrossProduct( vr, vf, vup );
+
+	m[0][0] = vr[0];
+	m[1][0] = vr[1];
+	m[2][0] = vr[2];
+
+	m[0][1] = vup[0];
+	m[1][1] = vup[1];
+	m[2][1] = vup[2];
+
+	m[0][2] = vf[0];
+	m[1][2] = vf[1];
+	m[2][2] = vf[2];
+
+	memcpy( im, m, sizeof( im ) );
+
+	im[0][1] = m[1][0];
+	im[0][2] = m[2][0];
+	im[1][0] = m[0][1];
+	im[1][2] = m[2][1];
+	im[2][0] = m[0][2];
+	im[2][1] = m[1][2];
+
+	memset( zrot, 0, sizeof( zrot ) );
+	zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;
+
+	zrot[0][0] = cos( DEG2RAD( degrees ) );
+	zrot[0][1] = sin( DEG2RAD( degrees ) );
+	zrot[1][0] = -sin( DEG2RAD( degrees ) );
+	zrot[1][1] = cos( DEG2RAD( degrees ) );
+
+	R_ConcatRotations( m, zrot, tmpmat );
+	R_ConcatRotations( tmpmat, im, rot );
+
+	for ( i = 0; i < 3; i++ )
+	{
+		dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2];
+	}
+}
+
+void AngleVectors (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;
+	}
+}
+
+
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
+{
+	float d;
+	vec3_t n;
+	float inv_denom;
+
+	inv_denom = 1.0F / DotProduct( normal, normal );
+
+	d = DotProduct( normal, p ) * inv_denom;
+
+	n[0] = normal[0] * inv_denom;
+	n[1] = normal[1] * inv_denom;
+	n[2] = normal[2] * inv_denom;
+
+	dst[0] = p[0] - d * n[0];
+	dst[1] = p[1] - d * n[1];
+	dst[2] = p[2] - d * n[2];
+}
+
+/*
+** assumes "src" is normalized
+*/
+void PerpendicularVector( vec3_t dst, const vec3_t src )
+{
+	int	pos;
+	int i;
+	float minelem = 1.0F;
+	vec3_t tempvec;
+
+	/*
+	** find the smallest magnitude axially aligned vector
+	*/
+	for ( pos = 0, i = 0; i < 3; i++ )
+	{
+		if ( fabs( src[i] ) < minelem )
+		{
+			pos = i;
+			minelem = fabs( src[i] );
+		}
+	}
+	tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+	tempvec[pos] = 1.0F;
+
+	/*
+	** project the point onto the plane defined by src
+	*/
+	ProjectPointOnPlane( dst, tempvec, src );
+
+	/*
+	** normalize the result
+	*/
+	VectorNormalize( dst );
+}
+
+
+
+/*
+================
+R_ConcatRotations
+================
+*/
+void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+}
+
+
+/*
+================
+R_ConcatTransforms
+================
+*/
+void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+				in1[0][2] * in2[2][3] + in1[0][3];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+				in1[1][2] * in2[2][3] + in1[1][3];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+	out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+				in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+/*
+===============
+LerpAngle
+
+===============
+*/
+float LerpAngle (float a2, float a1, float frac)
+{
+	if (a1 - a2 > 180)
+		a1 -= 360;
+	if (a1 - a2 < -180)
+		a1 += 360;
+	return a2 + frac * (a1 - a2);
+}
+
+
+float	anglemod(float a)
+{
+/*
+	if (a >= 0)
+		a -= 360*(int)(a/360);
+	else
+		a += 360*( 1 + (int)(-a/360) );
+*/
+	a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+	return a;
+}
+
+	int		i;
+	vec3_t	corners[2];
+
+
+// this is the slow, general version
+int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, cplane_t *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;
+}
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+==================
+*/
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, cplane_t *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
+		assert( 0 );
+		break;
+	}
+
+	sides = 0;
+	if (dist1 >= p->dist)
+		sides = 1;
+	if (dist2 < p->dist)
+		sides |= 2;
+
+	assert( sides != 0 );
+
+	return sides;
+}
+
+void ClearBounds (vec3_t mins, vec3_t maxs)
+{
+	mins[0] = mins[1] = mins[2] = 99999;
+	maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs)
+{
+	int		i;
+	vec_t	val;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		val = v[i];
+		if (val < mins[i])
+			mins[i] = val;
+		if (val > maxs[i])
+			maxs[i] = val;
+	}
+}
+
+
+int VectorCompare (vec3_t v1, vec3_t v2)
+{
+	if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2])
+			return 0;
+			
+	return 1;
+}
+
+
+vec_t VectorNormalize (vec3_t v)
+{
+	float	length, ilength;
+
+	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+	length = sqrt (length);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		v[0] *= ilength;
+		v[1] *= ilength;
+		v[2] *= ilength;
+	}
+		
+	return length;
+
+}
+
+vec_t VectorNormalize2 (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);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		out[0] = v[0]*ilength;
+		out[1] = v[1]*ilength;
+		out[2] = v[2]*ilength;
+	}
+		
+	return length;
+
+}
+
+void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
+{
+	vecc[0] = veca[0] + scale*vecb[0];
+	vecc[1] = veca[1] + scale*vecb[1];
+	vecc[2] = veca[2] + scale*vecb[2];
+}
+
+
+vec_t _DotProduct (vec3_t v1, vec3_t v2)
+{
+	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]-vecb[0];
+	out[1] = veca[1]-vecb[1];
+	out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]+vecb[0];
+	out[1] = veca[1]+vecb[1];
+	out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy (vec3_t in, vec3_t out)
+{
+	out[0] = in[0];
+	out[1] = in[1];
+	out[2] = in[2];
+}
+
+void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross)
+{
+	cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
+	cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
+	cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+
+double sqrt(double x);
+
+vec_t VectorLength(vec3_t v)
+{
+	int		i;
+	float	length;
+	
+	length = 0;
+	for (i=0 ; i< 3 ; i++)
+		length += v[i]*v[i];
+	length = sqrt (length);		// FIXME
+
+	return length;
+}
+
+void VectorInverse (vec3_t v)
+{
+	v[0] = -v[0];
+	v[1] = -v[1];
+	v[2] = -v[2];
+}
+
+void VectorScale (vec3_t in, vec_t scale, vec3_t out)
+{
+	out[0] = in[0]*scale;
+	out[1] = in[1]*scale;
+	out[2] = in[2]*scale;
+}
+
+//====================================================================================
+
+/*
+============
+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 (char *in, char *out)
+{
+	while (*in && *in != '.')
+		*out++ = *in++;
+	*out = 0;
+}
+
+/*
+============
+COM_FileExtension
+============
+*/
+char *COM_FileExtension (char *in)
+{
+	static char exten[8];
+	int		i;
+
+	while (*in && *in != '.')
+		in++;
+	if (!*in)
+		return "";
+	in++;
+	for (i=0 ; i<7 && *in ; i++,in++)
+		exten[i] = *in;
+	exten[i] = 0;
+	return exten;
+}
+
+/*
+============
+COM_FileBase
+============
+*/
+void COM_FileBase (char *in, char *out)
+{
+	char *s, *s2;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '.')
+		s--;
+	
+	for (s2 = s ; s2 != in && *s2 != '/' ; s2--)
+	;
+	
+	if (s-s2 < 2)
+		out[0] = 0;
+	else
+	{
+		s--;
+		strncpy (out,s2+1, s-s2);
+		out[s-s2] = 0;
+	}
+}
+
+/*
+============
+COM_FilePath
+
+Returns the path up to, but not including the last /
+============
+*/
+void COM_FilePath (char *in, char *out)
+{
+	char *s;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '/')
+		s--;
+
+	strncpy (out,in, s-in);
+	out[s-in] = 0;
+}
+
+
+/*
+==================
+COM_DefaultExtension
+==================
+*/
+void COM_DefaultExtension (char *path, char *extension)
+{
+	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--;
+	}
+
+	strcat (path, extension);
+}
+
+/*
+============================================================================
+
+					BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+
+qboolean	bigendien;
+
+// can't just use function pointers, or dll linkage can
+// mess up when qcommon is included in multiple places
+short	(*_BigShort) (short l);
+short	(*_LittleShort) (short l);
+int		(*_BigLong) (int l);
+int		(*_LittleLong) (int l);
+float	(*_BigFloat) (float l);
+float	(*_LittleFloat) (float l);
+
+short	BigShort(short l){return _BigShort(l);}
+short	LittleShort(short l) {return _LittleShort(l);}
+int		BigLong (int l) {return _BigLong(l);}
+int		LittleLong (int l) {return _LittleLong(l);}
+float	BigFloat (float l) {return _BigFloat(l);}
+float	LittleFloat (float l) {return _LittleFloat(l);}
+
+short   ShortSwap (short l)
+{
+	byte    b1,b2;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+
+	return (b1<<8) + b2;
+}
+
+short	ShortNoSwap (short l)
+{
+	return l;
+}
+
+int    LongSwap (int l)
+{
+	byte    b1,b2,b3,b4;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+	b3 = (l>>16)&255;
+	b4 = (l>>24)&255;
+
+	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int	LongNoSwap (int l)
+{
+	return l;
+}
+
+float FloatSwap (float f)
+{
+	union
+	{
+		float	f;
+		byte	b[4];
+	} dat1, dat2;
+	
+	
+	dat1.f = f;
+	dat2.b[0] = dat1.b[3];
+	dat2.b[1] = dat1.b[2];
+	dat2.b[2] = dat1.b[1];
+	dat2.b[3] = dat1.b[0];
+	return dat2.f;
+}
+
+float FloatNoSwap (float f)
+{
+	return f;
+}
+
+/*
+================
+Swap_Init
+================
+*/
+void Swap_Init (void)
+{
+	byte	swaptest[2] = {1,0};
+
+// set the byte swapping variables in a portable manner	
+	if ( *(short *)swaptest == 1)
+	{
+		bigendien = false;
+		_BigShort = ShortSwap;
+		_LittleShort = ShortNoSwap;
+		_BigLong = LongSwap;
+		_LittleLong = LongNoSwap;
+		_BigFloat = FloatSwap;
+		_LittleFloat = FloatNoSwap;
+	}
+	else
+	{
+		bigendien = true;
+		_BigShort = ShortNoSwap;
+		_LittleShort = ShortSwap;
+		_BigLong = LongNoSwap;
+		_LittleLong = LongSwap;
+		_BigFloat = FloatNoSwap;
+		_LittleFloat = FloatSwap;
+	}
+
+}
+
+
+
+/*
+============
+va
+
+does a varargs printf into a temp buffer, so I don't need to have
+varargs versions of all text functions.
+FIXME: make this buffer size safe someday
+============
+*/
+char	*va(char *format, ...)
+{
+	va_list		argptr;
+	static char		string[1024];
+	
+	va_start (argptr, format);
+	vsprintf (string, format,argptr);
+	va_end (argptr);
+
+	return string;	
+}
+
+
+char	com_token[MAX_TOKEN_CHARS];
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+==============
+*/
+char *COM_Parse (char **data_p)
+{
+	int		c;
+	int		len;
+	char	*data;
+
+	data = *data_p;
+	len = 0;
+	com_token[0] = 0;
+	
+	if (!data)
+	{
+		*data_p = NULL;
+		return "";
+	}
+		
+// skip whitespace
+skipwhite:
+	while ( (c = *data) <= ' ')
+	{
+		if (c == 0)
+		{
+			*data_p = NULL;
+			return "";
+		}
+		data++;
+	}
+	
+// skip // comments
+	if (c=='/' && data[1] == '/')
+	{
+		while (*data && *data != '\n')
+			data++;
+		goto skipwhite;
+	}
+	
+
+// handle quoted strings specially
+	if (c == '\"')
+	{
+		data++;
+		while (1)
+		{
+			c = *data++;
+			if (c=='\"' || !c)
+			{
+				com_token[len] = 0;
+				*data_p = data;
+				return com_token;
+			}
+			if (len < MAX_TOKEN_CHARS)
+			{
+				com_token[len] = c;
+				len++;
+			}
+		}
+	}
+
+// parse a regular word
+	do
+	{
+		if (len < MAX_TOKEN_CHARS)
+		{
+			com_token[len] = c;
+			len++;
+		}
+		data++;
+		c = *data;
+	} while (c>32);
+
+	if (len == MAX_TOKEN_CHARS)
+	{
+//		Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
+		len = 0;
+	}
+	com_token[len] = 0;
+
+	*data_p = data;
+	return com_token;
+}
+
+
+/*
+===============
+Com_PageInMemory
+
+===============
+*/
+int	paged_total;
+
+void Com_PageInMemory (byte *buffer, int size)
+{
+	int		i;
+
+	for (i=size-1 ; i>0 ; i-=4096)
+		paged_total += buffer[i];
+}
+
+
+
+/*
+============================================================================
+
+					LIBRARY REPLACEMENT FUNCTIONS
+
+============================================================================
+*/
+
+void Com_sprintf (char *dest, int size, char *fmt, ...)
+{
+	va_list		argptr;
+	char	bigbuffer[0x10000];
+
+	va_start (argptr,fmt);
+	vsprintf (bigbuffer,fmt,argptr);
+	va_end (argptr);
+
+	//if (len >= size)
+	//	Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
+
+	strncpy (dest, bigbuffer, size-1);
+}
+
+/*
+=====================================================================
+
+  INFO STRINGS
+
+=====================================================================
+*/
+
+/*
+===============
+Info_ValueForKey
+
+Searches the string for the given
+key and returns the associated value, or an empty string.
+===============
+*/
+char *Info_ValueForKey (char *s, char *key)
+{
+	char	pkey[512];
+	static	char value[2][512];	// use two buffers so compares
+								// work without stomping on each other
+	static	int	valueindex;
+	char	*o;
+	
+	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)
+		{
+			if (!*s)
+				return "";
+			*o++ = *s++;
+		}
+		*o = 0;
+
+		if (!strcmp (key, pkey) )
+			return value[valueindex];
+
+		if (!*s)
+			return "";
+		s++;
+	}
+}
+
+void Info_RemoveKey (char *s, char *key)
+{
+	char	*start;
+	char	pkey[512];
+	char	value[512];
+	char	*o;
+
+	if (strstr (key, "\\"))
+	{
+//		Com_Printf ("Can't use a key with a \\\n");
+		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 (char *s)
+{
+	if (strstr (s, "\""))
+		return false;
+	if (strstr (s, ";"))
+		return false;
+	return true;
+}
+
+void Info_SetValueForKey (char *s, char *key, char *value)
+{
+	char	newi[MAX_INFO_STRING], *v;
+	int		c;
+	int		maxsize = MAX_INFO_STRING;
+
+	if (strstr (key, "\\") || strstr (value, "\\") )
+	{
+		Com_Printf ("Can't use keys or values with a \\\n");
+		return;
+	}
+
+	if (strstr (key, ";") )
+	{
+		Com_Printf ("Can't use keys or values with a semicolon\n");
+		return;
+	}
+
+	if (strstr (key, "\"") || strstr (value, "\"") )
+	{
+		Com_Printf ("Can't use keys or values with a \"\n");
+		return;
+	}
+
+	if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1)
+	{
+		Com_Printf ("Keys and values must be < 64 characters.\n");
+		return;
+	}
+	Info_RemoveKey (s, key);
+	if (!value || !strlen(value))
+		return;
+
+	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+	if (strlen(newi) + strlen(s) > maxsize)
+	{
+		Com_Printf ("Info string length exceeded\n");
+		return;
+	}
+
+	// only copy ascii values
+	s += strlen(s);
+	v = newi;
+	while (*v)
+	{
+		c = *v++;
+		c &= 127;		// strip high bits
+		if (c >= 32 && c < 127)
+			*s++ = c;
+	}
+	*s = 0;
+}
+
+//====================================================================
+
+
--- /dev/null
+++ b/doc/crbot/README.doc
@@ -1,0 +1,41 @@
+Name of Mod : CRBot, for Quake II
+              Source code 
+File Name   : crbot_ctf.zip
+Version     : 1.0
+Date        : March 26, 2000
+Author      : Mike Malakhov
+E-mail      : [email protected]
+Web Site    : http://crbot.nikto.net
+Coding time : about 250 hours total, excluding testing
+
+CREDITS:
+--------
+Quake2, source code for .dll -- ID Software, http://www.idsoftware.com/
+VWep, Viewable Weapons Patch -- Hentai, http://www.telefragged.com/tsunami/
+
+
+FORMAT
+------
+It was compiled using Visual C++ v5.0. 
+
+
+COMMENTS
+--------
+ This is the final source code for latest version of CRbot! Including CTF
+and full teamplay support. Enjoy.
+ This package includes not only the source code for CRbot, but all related 
+files from original ID Software source code distribution as well. I changed 
+some of them and don't exactly remember which ones, sorry you'll have to figure\
+it out yourself too. :)
+ This source code provided AS IS, with no warranty of any kind, blah blah 
+blah. Feel free to do whatever you want with it, there is only one condition:
+I don't provide any kind of support, I don't answer questions and I don't 
+tutor on how to create mods for Quake2. Otherwise I would be glad to
+hear about any further modifications you'll do to it. And of course it is 
+covered by original ID Software license, which you can find in the file named
+LICENSE.TXT. 
+ And one last thing: if you'll decide to use CRbot's source code in your own 
+mod, don't forget to give me some credit, ok? Or, you know, your karma might 
+suffer. :) Or at least send me your site's URL, so I can link to it from 
+CRbot's main page.
+
--- /dev/null
+++ b/doc/crbot/autoexec.cfg
@@ -1,0 +1,37 @@
+// -----------   autoexec.cfg config by Grieve  -----------
+
+set bot_menu 1
+set bot_chat 1
+set bot_taunt 1
+set map_cycle 1
+set no_hook 0
+set ctf 0
+
+set view_weapons 1
+bind "n" "notarget"
+
+alias bdroid "exec bots/bdroid.cfg"
+alias carmack "exec bots/carmack.cfg"
+alias ddz "exec bots/ddz.cfg"
+alias duke "exec bots/duke.cfg"
+alias ichabod "exec bots/ichabod.cfg"
+alias marine "exec bots/marine.cfg"
+alias mynx "exec bots/mynx.cfg"
+alias reaper "exec bots/reaper.cfg"
+alias sas "exec bots/sas.cfg"
+alias spawn "exec bots/spawn.cfg"
+alias startrooper "exec bots/startrooper.cfg"
+alias stormy "exec bots/stormy.cfg"
+alias yohko "exec bots/yohko.cfg"
+alias alita "exec bots/alita.cfg"
+alias spiff "exec bots/spiff.cfg"
+alias rylah "exec bots/rylah.cfg"
+alias pretty "exec bots/pretty.cfg"
+
+
+alias botlist "exec bots/botlist.cfg"
+echo ""
+echo "Type Botlist for a list of available Bots"
+echo ""
+wait
+
--- /dev/null
+++ b/doc/crbot/bots.cfg
@@ -1,0 +1,24 @@
+//         name
+//         |                 skill (0,1..10)
+//         |                 |   model
+//         |                 |   |        skin
+//         |                 |   |        |               team no.   
+//         |                 |   |        |               |
+sv addbot "voodoo"           6   female  "female/voodoo"  0
+wait
+sv addbot "ultimate killer"  10  male    "male/sniper"    0
+wait
+sv addbot "grunt#1"          7   male    "male/grunt"     1
+wait
+sv addbot "grunt#2"          6   male    "male/grunt"     1
+wait
+sv addbot "grunt#3"          5   male    "male/grunt"     1
+wait
+sv addbot "razor"            0   male    "male/razor"     0
+wait
+sv addbot "venus"            0   female  "female/venus"   0
+wait
+sv addbot "cyborg"           0   cyborg  "cyborg/ps9000"  0
+wait
+sv addbot "spawn"           0   Spawn  "Spawn/Spawn"  9
+wait
--- /dev/null
+++ b/doc/crbot/bots/alita.cfg
@@ -1,0 +1,2 @@
+sv addbot   "alita"    9  alita   "alita/shelivith2"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/bdroid.cfg
@@ -1,0 +1,2 @@
+sv addbot   "Bdroid"    8  droid   "droid/blasted"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/botlist.cfg
@@ -1,0 +1,24 @@
+// -----------   Botlist.cfg by Grieve  -----------
+
+echo ""
+echo "Available Bots :"
+echo "----------------"
+echo ""
+echo "bdroid           alita"
+echo "carmack          pretty"
+echo "ddz              rylah"
+echo "duke"
+echo "ichabod"
+echo "marine"
+echo "mynx"
+echo "reaper"
+echo "sas"
+echo "spawn"
+echo "startrooper"
+echo "stormy"
+echo "yohko"
+echo "spiff"
+echo ""
+echo ""
+echo "Just type in the Name of the Bot you want."
+
--- /dev/null
+++ b/doc/crbot/bots/bots.txt
@@ -1,0 +1,44 @@
+"sv" "addbot"   "Spawn"             "spawn/spawn"       "bots/spawn_c.c"  "spawn"
+"sv" "addbot"   "Dr.Freak"          "drfreak/bmovie"    "bots/hunk_c.c"   "hunk"
+"sv" "addbot"   "Knight"            "pknight/knight"    "bots/hunk_c.c"   "hunk"
+"sv" "addbot"   "Bill Gates"        "alien/alien"       "bots/bill_c.c"   "bill"
+"sv" "addbot"   "Marine"            "marine/urban"      "bots/java_c.c"   "java"
+"sv" "addbot"   "Startrooper"       "terran/black"      "bots/trooper_c.c" "trooper"
+"sv" "addbot"   "Stormy"            "crafty/ruby"       "bots/omicro_c.c" "omicron"
+"sv" "addbot"   "crafty"            "crafty/crafty"     "bots/omicro_c.c" "omicron"
+"sv" "addbot"   "Yohko"             "yohko/Anubis"      "bots/laura_c.c"  "laura"
+"sv" "addbot"   "Purgy"             "purgatory/purgi1"  "bots/laura_c.c"  "laura"
+"sv" "addbot"   "@"                 "@/azure"           "bots/java_c.c"   "java"
+"sv" "addbot"   "Grey"              "grey/Agrey"        "bots/luuzr_c.c"  "luuzr"
+"sv" "addbot"   "Faerie"            "faerie/dpfyuri"    "bots/maxine_c.c" "maxine"
+"sv" "addbot"   "Lara"              "sydney/amber"      "bots/bitch_c.c"  "bitch"
+"sv" "addbot"   "Bitch"             "WitchBlade/sara"   "bots/bitch_c.c"  "bitch"
+"sv" "addbot"   "Hastewhore"        "Hast3975/melissa"  "bots/bitch_c.c"  "bitch"
+"sv" "addbot"   "Buffy"             "Hast3975/buffy"    "bots/bitch_c.c"  "bitch"
+"sv" "addbot"   "Battledroid"       "bdroid/security"   "bots/no9_c.c"    "no9"
+"sv" "addbot"   "Betadroid"         "droid/blasted"     "bots/no9_c.c"    "no9"
+"sv" "addbot"   "Stroggie"          "gunner/gunner"     "bots/no9_c.c"    "no9"
+"sv" "addbot"   "Guard"             "guard/LGUARD"      "bots/no9_c.c"    "no9"
+"sv" "addbot"   "Johnny5"           "Johnny5/Johnny5"   "bots/no9_c.c"    "no9"
+"sv" "addbot"   "Epsilon"           "male/kickmale"     "bots/player_c.c" "player"
+"sv" "addbot"   "Reaper"            "Droideka/droideka" "bots/reaper_c.c" "reaper"
+"sv" "addbot"   "DDZ"               "ddz/ddz"           "bots/reaper_c.c" "reaper"
+"sv" "addbot"   "Ripley"            "female/ripley"     "bots/messia_c.c" "messiah"
+"sv" "addbot"   "Twilight"       "twilight/whiterabbit" "bots/messia_c.c" "messiah"
+"sv" "addbot"   "Mynx"              "female/mynx"       "bots/babe_c.c"   "babe"
+"sv" "addbot"   "Kitana"            "female/khameleon"  "bots/babe_c.c"   "babe"
+"sv" "addbot"   "Pretty"            "Pretty/darkgirl"   "bots/babe_c.c"   "babe"
+"sv" "addbot"   "Blade"             "Blade/blade"       "bots/sparta_c.c" "spartacus"
+"sv" "addbot"   "Duke"              "Duke/dukenukem"    "bots/duke_c.c"   "duke"
+"sv" "addbot"   "SAS"               "sas/urban"         "bots/sas_c.c"    "sas"
+"sv" "addbot"   "Femwolf"           "fwwolf/katerina"   "bots/trash_c.c"  "trash"
+"sv" "addbot"   "Cyb"               "cyborg/oni911"     "bots/byte_c.c"   "byte"
+"sv" "addbot"   "Tinkywinky"        "laalaa/tinkywinky" "bots/zero_c.c"   "zero"
+"sv" "addbot"   "Waste"             "waste/happyboy"    "bots/hunk_c.c"   "hunk"
+"sv" "addbot"   "Alita"             "alita/shelivith2"  "bots/babe_c.c"   "babe"
+"sv" "addbot"   "Rylah"             "ryla/ashen"        "bots/babe_c.c"   "babe"
+"sv" "addbot"   "Ai"                "ai/ai"             "bots/messia_c.c" "messiah"
+"sv" "addbot"   "Eva-01"            "Eva-01/ctf_b"      "bots/reaper_c.c" "reaper"
+"sv" "addbot"   "Spiff"             "spiff/spiff"       "bots/zero_c.c"   "zero"
+"sv" "addbot"   "Chuckie"           "chuckie_f/Chuckie" "bots/zero_c.c"   "zero"
+
--- /dev/null
+++ b/doc/crbot/bots/carmack.cfg
@@ -1,0 +1,2 @@
+sv addbot   "Carmack"   9    carmack    "carmack/john" 0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/ddz.cfg
@@ -1,0 +1,2 @@
+sv addbot   "ddz"        10    ddz   "ddz/ddz"  0
+wait
--- /dev/null
+++ b/doc/crbot/bots/duke.cfg
@@ -1,0 +1,2 @@
+sv addbot  "duke"     10   Duke   "Duke/dukenukem"   0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/ichabod.cfg
@@ -1,0 +1,2 @@
+sv addbot  "ichabod"     9    ichabod   "ichabod/ichabod"   0
+wait
--- /dev/null
+++ b/doc/crbot/bots/marine.cfg
@@ -1,0 +1,2 @@
+"sv" "addbot"   "Marine"      9    marine   "marine/urban"    0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/mynx.cfg
@@ -1,0 +1,2 @@
+sv addbot "mynx"        9   female  "female/mynx"   0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/pretty.cfg
@@ -1,0 +1,2 @@
+sv addbot   "Pretty"    8  Pretty   "Pretty/darkgirl"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/reaper.cfg
@@ -1,0 +1,2 @@
+sv addbot  "reaper"        10    Droideka  "Droideka/droideka"  0
+wait
--- /dev/null
+++ b/doc/crbot/bots/rylah.cfg
@@ -1,0 +1,2 @@
+sv addbot   "ryla"    0  ryla   "ryla/ashen"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/sas.cfg
@@ -1,0 +1,2 @@
+sv addbot  "sas"   9  sas  "sas/sas-urban"   0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/spawn.cfg
@@ -1,0 +1,2 @@
+sv addbot  "spawn"        10    Spawn  "Spawn/Spawn"  0
+wait
--- /dev/null
+++ b/doc/crbot/bots/spiff.cfg
@@ -1,0 +1,2 @@
+sv addbot   "Spiff"    3  spiff   "spiff/spiff"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/startrooper.cfg
@@ -1,0 +1,2 @@
+"sv" "addbot"   "Startrooper"      10    terran   "terran/rusted"    0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/stormy.cfg
@@ -1,0 +1,2 @@
+sv addbot  "stormy"      10   crafty    "crafty/ruby"   0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/bots/yohko.cfg
@@ -1,0 +1,2 @@
+sv addbot   "Yohko"    0  yohko   "yohko/Anubis"     0
+wait
\ No newline at end of file
--- /dev/null
+++ b/doc/crbot/crbot.txt
@@ -1,0 +1,308 @@
+Updated by Grieve (Marc Kerschbaumer) and DUB (Peter Chiochetti) 2003/2004
+
+  - Several code cleanups (pure C code now)
+
+  - vweapon bug fixed, if vweapons are missing the default weapon
+     is drawn instead of the "diamond"
+
+  - bot now affected by trigger_push, will use jumppads sometimes.
+
+  - Ingame menu in Linux fixed.
+
+  - Linux makefile added; and updated project files for Visual Studio 6 and LCC win32.
+
+
+=======================================================================================
+
+
+Name of Mod : The CRBot, for Quake II
+File Name   : crbot114.zip
+Version     : 1.14
+Date        : April 23, 1998
+Author      : Mike Malakhov
+E-mail      : [email protected]
+              [email protected]
+Web Site    : http://planetquake.com/crbot
+              http://www.pobox.com/~ww/bot.html
+Build time  : >210 hours
+Source code : 180 Kb
+
+CREDITS:
+--------
+Quake2, source code for .dll -- ID Software, http://www.idsoftware.com/
+ThreeWave CTF, source code   -- Zoid, ftp://ftp.idsoftware.com/idstuff/quake2/ctf/
+VWep, Viewable Weapons Patch -- Hentai, http://www.telefragged.com/tsunami/
+
+
+AUTHOR INFO
+-----------
+My name's Mike Malakhov, you can get more info about me 
+on my homepage at http://www.pobox.com/~ww/
+
+
+TYPE OF MOD 
+-----------
+DLL        : YES
+CTF support: YES
+Ingame menu: YES
+Sound      : NO
+Maps       : NO
+Graphics   : NO
+
+
+FORMAT
+------
+It was compiled using Visual C++ v5.0. 
+You MUST install patch v3.13 (or later) for Quake2 to play this 
+version of CRbot!
+
+
+INSTALLATION
+------------
+Unzip this file with subdirectories in your Quake2 dir. For example, if your 
+Quake2 directory is d:\quake2\ after unzipping you will get d:\quake2\crbot\ 
+subdirectory. Then copy your standard shortcut for running Quake2 into a new 
+one and then add 
+ +set game "crbot" +set deathmatch 1
+to the command line.
+
+If you want to play CTF, add "+set ctf 1", otherwise use "+set ctf 0".
+And don't forget to copy PAK0.PAK from CTF into CRbot subdirectory.
+
+
+COMMANDS
+--------
+
+sv addbots <skill> <amount> -- to spawn several bots; <skill> could be a number
+                          from 1(easy) to 10(most advanced), or could be a 
+                          range like this: <min_skill>..<max_skill>
+                          that'll allow you to create random set of bots with 
+                          different skills; example:  addbots 3..6 5
+                          it'll create 5 bots with random skills from 3 to 6;
+                          skill 0 has special meaning -- bot with this skill 
+                          level will be adjusting his abilities dynamically
+                          to match player's skills;
+
+sv addbot "<name>" <skill> "<model name>" "<skin name>" <team_no>
+              -- add a bot, where <skill> is a number from 1(low) to
+                 10(most advanced); 0 is special "adjusting" skill level; 
+                 you can repeat this command to add more bots; you can also 
+                 use this command in .CFG files (see BOTS.CFG for example 
+                 how to do this) to create predefined set of bots by typing 
+                 "exec bots.cfg" in console window; 
+
+sv killbot <name> -- remove bot with name <name> from the game permanently;
+
+bot_team # -- (# is a number from 0 to 9) you can assign next created bot to 
+              specified team; bots from the same team don't attack each other 
+              unless they'll accidentally hurt each other;
+              team 0 has special meaning, all bots in that team fight for 
+              themselves (default team).
+
+cmd join_team #  -- put yourself into the team number # together with bots;
+
+bot_skin <name>  -- default is "male/viper"; as bot_team command this one 
+                    presets the skin for all bots created afterwards;
+
+bot_model <name> -- default is "male";
+
+no_tech 1/0      -- disable/enable CTF style techs in normal deathmatch game;
+
+no_hook 1/0      -- disable/enable grappling hook in normal deathmatch game;
+
+cmd save_nodemap -- save path nodes structure into external file;
+
+
+Note: All commands with "sv" prefix will be effective only if 
+      typed on server;
+      All 'bot_' vars will be saved in config.cfg.
+
+
+For example, if you want to start the game with 8 bots, two female ones in one 
+team with skill 3, three males in another team with skills between 3 and 7, 
+three individual bots with skills 6, 8 and 10 and names, and assign distinctive 
+skins to every team and to individual bots, you have to type in console window:
+
+ bot_model "female"
+ bot_skin "female/voodoo"
+ bot_team 1
+ addbots 3 2
+
+ bot_model "male"
+ bot_skin "male/viper"
+ bot_team 2
+ addbots 3..7 3
+
+ cmd addbot scout           6   "male"    "male/flak"     0
+ cmd addbot bitch           8   "female"  "female/venus"  0
+ cmd addbot "ultimate bot"  10  "male"    "male/sniper"   0
+
+
+CTF SUPPORT
+-----------
+
+If you want to play CTF, you will need to copy PAK0.PAK from CTF into CRbot 
+subdirectory, and then add "+set ctf 1" to your command line.
+Or you can change "ctf" cvar from console. Don't forget 
+to restart the map after that.
+
+Team #1 is the RED team, and team #2 is the BLUE team. So if you want to add
+bots to Red team, use "bot_team 1", or if you want them to be in Blue team,
+use "bot_team 2". If no team number assigned or it's not equal to 1 or 2, bots will 
+be evenly placed in both team.
+
+
+TEAM CONTROL
+------------
+If you are in the team (by using "cmd join_team" command) then you
+can use following commands to control your team:
+
+cmd team_help  -- summon all members of your team to your current location,
+                  will not affect bots which are engaged in melee already;
+
+cmd team_group -- this will force all bots in your team to get in tight 
+                  formation and follow you as a leader; 
+
+cmd team_free [bot name] -- dismiss named bot or whole current formation if
+                             no name specified; 
+cmd team_guard [bot name] -- order named bot to guard the area; if no name
+                             specified this assignment will be given to
+                             all visible bots in front of you;
+cmd team_patrol [bot name] -- same as team_guard but slightly more freedom
+                              for bots;
+
+MENU
+----
+
+ You can use menu system to edit different settings and to create
+bots. To access it use your "inventory" key -- which is <TAB> by default.
+To navigate menu use "[" and "]" keys ("next in inventory" and "previous in
+inventory" keys) and to select menu item use <ENTER> (or your "use inventory"
+key).
+
+
+MISC. COMMANDS
+--------------
+
+cmd bot_stats -- show information about all bots in the game;
+
+cmd team_stats -- show team scores;
+
+notarget -- this command will toggle "observer mode";
+
+view_weapons 0/1 -- toggle viewable weapons support (VWep patch); in order to be
+                    able to use it, goto http://www.telefragged.com/tsunami/
+                    download VWep patch, install it and then copy PAK2.PAK
+                    into CRbot directory;
+
+bot_chat 0/1 -- set this var to 0 if you don't want to hear anything from bots,
+                default is 1;
+
+obituary_msgs 0/1 -- deafult is 1; if it's set to 0 you will not see any
+                     "bot killed by bot", "human killed by bot", etc., messages;
+
+bot_menu 0/1 -- set var to 0 if you don't want bot menu; useful for dedicated
+                servers to prevent users from adding new bots;
+
+bot_taunt 0/1 -- set this var to 0 if you don't want taunt/flip animation,
+                 default is 1;
+
+
+EXTERNAL CONFIG FILES
+---------------------
+
+ There are four text files you can edit which contain lines bot will say 
+in different situations:
+
+DEATH.TXT -- when bot dies
+KILL.TXT  -- when bot kills somebody
+PAIN.TXT  -- when bot gets hurt by somebody
+FIGHT.TXT -- random saying during melee
+
+
+
+FREQUENTLY ASKED QUESTIONS:
+-------------------------
+
+Q: I can't run Quake2, it says "Error during initialization" or 
+   "ERROR: game is version 3, not #". What's wrong?
+A: You need to install patch v3.13 or later for your Quake2. 
+
+Q: I've installed everything as you said, but can't create any bots, game 
+   doesn't understand "addbot" or "addbots" commands. Why?
+A: Make sure you DO NOT have gamex86.dll in base Quake2 directory. It should
+   be located only in QUAKE2/BASEQ2 and/or QUAKE2/CRBOT subdirs.
+
+Q: What about Linux and Alpha port?
+A: Yes, eventually I'm going to do it. But not right now.
+
+Q1: I've done everything as you said, but can't create the bot. Why?
+Q2: You bot is great, but how do I play it?
+Q3: Where do I find Quake2 shortcut and how to add "+set game crbot" 
+    to the command line?
+A: (by Jay Fortman <[email protected]>)
+    Double click on the .zip to open up your .zip program (presumably
+WinZip).  When the file names show up goto edit and "Select All" (or
+press Ctrl+A).  Press extract.  It will then ask for a location to
+extract the files to.  Enter: C:\Quake2 (where C: is the drive Q2 is
+installed on and assuming you didn't rename the directory from default
+Quake2).  The files will then be extracted to a new directory under
+Quake2 labeled CRBot automatically.  Future updates can be performed in
+the same manner--they will just overwrite the old files (and, as a
+suggestion, keep your old version in its original .zip just in case the
+new one doesn't work for you).  Now you'll need to RIGHT click (the
+mouse button) on the Taskbar at the bottom of the Desktop (that's that
+area between the Start button and clock that doesn't have
+taskbuttons--if you don't know that you don't belong on a computer).
+Select Properties.  A new window will pop up titled Taskbar
+Properties--select the Start Menu Programs tab just under the titlebar.
+Some new options come up--select the button labeled Advanced.  Assuming
+you didn't move the Q2 start menu shortcut open the plus next to
+Programs, you should now see Quake II.  Click on the Quake II folder and
+all of its contents will be displayed in the right window.  From the
+window with the Q2 contents, RIGHT click on Quake II -or- Quake II -
+Compatability Mode (whichever you prefer to use--I recommend the
+compatability mode myself).  Select the option COPY.  Now RIGHT click in
+the empty space of the same window and choose PASTE.  You should now
+have a COPY OF QUAKE II - COMPATABILITY MODE.  RIGHT click that and
+rename it whatever you like ("Quake II - CRBot" is my choice ; ).  Now
+RIGHT click it again and choose PROPERTIES.  Click on the SHORTCUT tab
+under the title bar.  In the window that pops up there should be an
+entry labeled TARGET.  Click in the text area and make sure you move the
+cursur all the way to your right (the end of the entry).  Add one space
+after the last entry and add the following:  +set game "crbot" +set
+deathmatch 1 .  So the TARGET should read something like this (where C:
+is the drive your Q2 is in and E: is your CD-rom drive):
+C:\Quake2\quake2.exe +set cddir E:\install\data +set basedir c:\QUAKE2
++set vid_ref soft +set vid_fullscreen 0 +set sw_mode 0 +set s_wavonly 1
++set game "crbot" +set deathmatch 1 .  If everything is set up just like
+that your copy of Quake II - CRBot should now be ready to run like the
+blood splattered on the wall after you were supremely gibbed.  Close all
+windows and then go through the start menu and select your CRBot
+shortcut.  BINGO!  To add bots to your working Q2 game simply follow the
+instructions posted in the README.TXT file extracted from the .zip file
+directly into your CRBot directory (may I suggest making a start menu
+shortcut for that too--it's simple, you can do it).  If you feel you
+need to peruse the contents of the readme and are in the middle of your
+Q2 game simply hold down ALT and then press TAB.  This will return you
+to windows without losing your game (most of the time--everything has
+bugs sometimes).  And that about sums it up fragies and fragmen.  I hope
+this was informative enough to help you out ;) .
+
+
+COPYRIGHT/PERMISSIONS
+----------------------
+This bot was solely created for my own use, therefore it is distributed AS IS 
+and I can not guarantee you anything, including your satisfaction. 
+I also will not take any responsibility for system crashes and/or any damage 
+or loss of data caused by bot's mod. I can however guarantee that I have not 
+intentionally added any malicious content to this application.
+This mod is in no way associated with id Software. 
+You may freely distribute this archive, as long as it remains intact.
+Commercial distribution or distribution on CDs is prohibited by ID Software's 
+End User License for Quake2 source code used in this mod.
+
+Enjoy,
+Mike Malakhov, [email protected]
+               [email protected]
+
--- /dev/null
+++ b/doc/crbot/death.txt
@@ -1,0 +1,23 @@
+Damn! I was just getting started...
+Argh! You got me, pardner...
+I'll be back.
+Join the army, they said...
+Game over, man!
+Are you happy now?
+Can't win 'em all...
+If you ain't dyin', you ain't tryin'.
+I'm gonna git ya, sucka!
+It is a good day to die.
+Sonuvagun!
+I should have just stayed in bed.
+Doh!
+Who wants to live forever anyway?
+Tell 'em I died with my boots on...
+Sold my soul to rock 'n' roll!
+Am I dead yet?
+Back to the drawing board...
+You know... you only killed me because in real life I'm actually blind
+Excuse me... that's my bloody carcass smeared all over the place!
+Today is a good day to die...
+Doh!
+Aaaahhhh....
--- /dev/null
+++ b/doc/crbot/fight.txt
@@ -1,0 +1,62 @@
+Stand still and make this easier for both of us!
+Die, scum!
+Hey, you want me to stand still and aim your weapon too?
+Incoming!
+Who wants some?
+Are we having fun yet?
+You. You want some of this?
+Gimme some sugar, baby!
+Hello!
+Hey man, you're pretty good - for target practice!
+Let's jam!
+You don't get out much, do you?
+Hi there!
+Say hello to my little friend!
+Ay caramba!
+Ooh, *THIS* gun is even better!
+Let's rock!
+Shove *THIS* in your pipe and smoke it!
+Doh!
+Is that fear I smell? Or is something brown running down your pants?
+Surprise!
+You're not so tough...
+Okay, enough pillow talk!
+Smokin!
+Let's go!
+Take this!
+Hey you! Wanna fight?
+C'mon!
+Kick ass!
+Rock 'n' roll!
+I'm here to kick ass and chew gum, and I'm fresh outta gum...
+Tag! You're it!
+Just die, will ya?
+I'm over here...
+Rock my world!
+Merry Christmas!
+Bullets for badasses! Have a free sample!
+Looking for me?
+I'm gonna mop the floor with you!
+I'm gonna hurt you so bad yer mama is gonna feel it
+Stand still and make this easier for both of us!
+Hey, you want me to stand still and aim your weapon too
+Since you're always running from me, I'll never recognize you face to face
+Who wants some?
+You. You want some of this?
+Gimme some sugar baby
+Hey man, you're pretty good - for target practice!
+Interface this!
+Don't make me have to break out the Turbolax!
+Why hunt when I'll do the hunting for ya?
+You'll have another place to shit once I'm through with you!
+Frag this, wonderboy!
+Ey, you don't get out much, do you?
+Hope you packed enough marshmellows for all of us!
+Say hello to my little friend!
+Ooh, that had to hurt
+*Where* did you stick that railgun?
+Ooh, *This* gun is even better!
+Shove THIS in your pipe and smoke it!
+Is that fear I can smell?
+Touchdown, in your face!
+It gives me digital pleasure to spread your guts all across this level!
--- /dev/null
+++ b/doc/crbot/kill.txt
@@ -1,0 +1,58 @@
+I'm not sure if you know this... but you suck!
+Later, sucker!
+SeeeYA!
+Next time, duck
+Thanks for making my day...
+Punk
+Game over!
+You're dog meat pal!
+I said it already and I say it again: you suck!
+Hasta la vista baby!
+It was a good day to die...
+Hey man, you're pretty good - for target practice!
+Eat my shorts!
+If I had feelings, I would pity you
+You should try avoiding my shots now and then
+Sorry, zero is the lowest skill setting I have!
+Who's your daddy?
+Die, scum!
+Bow down!
+Oops, sorry!
+Rise and fight as a Zombie!
+Wimp... cannot even beat a bot!
+Smells like burned wimp here!
+If you keep on playing like this, we, the bots, will soon rule this server!
+It feels so good to see mortal blood on my bot-feet!
+You suck.
+Rock 'n' roll!
+You know what?... you suck!
+Sayonara, sucker!
+There can be only one.
+Be seeing ya!
+Next time, duck...
+Thanks for making my day...
+Hasta la vista, baby!
+It was a good day to die... fer you!
+Hey man, you're pretty good - for target practice!
+Yes!
+Bet that hurt...
+You know, some practice might have helped. ...Naaah.
+I'm sorry, did I break your concentration?
+Smokin!
+I'm just a lean, mean, killing machine!
+Later dude!
+Catch ya later...
+Mess with the best, die like the rest!
+You suck didly-uck!
+You're history!
+Pathetic
+Miserable wimp!
+You had guts, kid. Too bad they're scattered to kingdom come...
+Cool!
+You snooze, you lose!
+You know what your problem is? You suck.
+That'll teach you, punk!
+Told ya I'd git ya, sucka...
+Groovy!
+Yee-hah!
+Stupid is as stupid does...
--- /dev/null
+++ b/doc/crbot/maplist.txt
@@ -1,0 +1,15 @@
+# list of maps to cycle through
+#  use "map_cycle 1" to turn it on
+#   or "map_cycle 2" to use them in random order
+q2dm1
+gotcha
+unrdm1
+bldstorm
+ledges
+millennium
+q2dm1
+q2dm3
+q2dm4
+q2dm5
+q2dm7
+q2dm8
--- /dev/null
+++ b/doc/crbot/pain.txt
@@ -1,0 +1,42 @@
+Not only was that a lucky shot, but your mother wears army boots!
+Nice shot! You got me while I was having a seizure at the keyboard!
+Your momma shoots better than you
+Nice shot for a rookie
+Is THAT the best you can do?
+You fight like a girlscout!
+Good one, eagle-eye!
+Good, you can see me. Now I can just kill you.
+Lucky shot!
+Is THAT the best you can do?
+OK, *NOW* take your best shot.
+Ow!
+You want to try that again?
+Yow!
+Betcha can't do that again...
+Ay caramba!
+It's only a flesh wound!
+Did you mean to do that, or was that just dumb luck?
+You can't hurt me...
+Oh yeah, sure, shoot me when I'm not ready...
+Doh!
+OK, my turn...
+You just made the biggest mistake of your life.
+Now *THAT* pisses me off!
+That didn't hurt.
+Medic! I'm hit!
+Argh!
+Mommy! They're picking on me again!
+Damn!
+Hurts so good!
+Sonuvagun!
+Aw, crap!
+Next time, finish me off with the first shot...
+This means war!
+Feeling hostile, are we?
+Ouch!
+Oooh, you make me very angry!
+Keep on rockin' me, baby!
+Damn you!
+Go ahead... make my day.
+Whoa!
+You talkin' to me?
--- /dev/null
+++ b/mkfile.crbot
@@ -1,0 +1,88 @@
+</$objtype/mkfile
+
+GAME=crbot
+TARG=q2$GAME
+LIB=$GAME/$GAME.$O.a
+
+OFILES=\
+	cl_cin.$O\
+	cl_ents.$O\
+	cl_fx.$O\
+	cl_newfx.$O\
+	cl_input.$O\
+	cl_inv.$O\
+	cl_main.$O\
+	cl_parse.$O\
+	cl_pred.$O\
+	cl_tent.$O\
+	cl_scrn.$O\
+	cl_view.$O\
+	console.$O\
+	fs.$O\
+	keys.$O\
+	menu.$O\
+	snd_dma.$O\
+	snd_mem.$O\
+	snd_mix.$O\
+	qmenu.$O\
+	sv_ccmds.$O\
+	sv_ents.$O\
+	sv_game.$O\
+	sv_init.$O\
+	sv_main.$O\
+	sv_send.$O\
+	sv_user.$O\
+	sv_world.$O\
+	cmd.$O\
+	cmodel.$O\
+	common.$O\
+	crc.$O\
+	cvar.$O\
+	files.$O\
+	md4.$O\
+	net_chan.$O\
+	pmove.$O\
+	cd.$O\
+	in.$O\
+	snd.$O\
+	sys.$O\
+	udp.$O\
+	vid.$O\
+	vmenu.$O\
+	r_aclip.$O\
+	r_alias.$O\
+	r_bsp.$O\
+	r_draw.$O\
+	r_edge.$O\
+	r_image.$O\
+	r_light.$O\
+	r_main.$O\
+	r_misc.$O\
+	r_model.$O\
+	r_part.$O\
+	r_poly.$O\
+	r_polyse.$O\
+	r_rast.$O\
+	r_scan.$O\
+	r_sprite.$O\
+	r_surf.$O\
+
+HFILES=\
+	adivtab.h\
+	anorms.h\
+	dat.h\
+	fns.h\
+	rand1k.h\
+
+BIN=$home/bin/$objtype
+</sys/src/cmd/mkone
+
+CFLAGS=-FTVw -I$GAME
+
+$LIB:V:
+	cd $GAME
+	mk
+
+clean nuke:V:
+	@{ cd $GAME; mk $target }
+	rm -f *.[$OS] [$OS].out $TARG
--- a/mkfile.ctf
+++ b/mkfile.ctf
@@ -1,7 +1,7 @@
 </$objtype/mkfile
 
-TARG=q2ctf
 GAME=ctf
+TARG=q2$GAME
 LIB=$GAME/$GAME.$O.a
 
 OFILES=\
--- /dev/null
+++ b/mkfile.rogue
@@ -1,0 +1,88 @@
+</$objtype/mkfile
+
+GAME=rogue
+TARG=q2$GAME
+LIB=$GAME/$GAME.$O.a
+
+OFILES=\
+	cl_cin.$O\
+	cl_ents.$O\
+	cl_fx.$O\
+	cl_newfx.$O\
+	cl_input.$O\
+	cl_inv.$O\
+	cl_main.$O\
+	cl_parse.$O\
+	cl_pred.$O\
+	cl_tent.$O\
+	cl_scrn.$O\
+	cl_view.$O\
+	console.$O\
+	fs.$O\
+	keys.$O\
+	menu.$O\
+	snd_dma.$O\
+	snd_mem.$O\
+	snd_mix.$O\
+	qmenu.$O\
+	sv_ccmds.$O\
+	sv_ents.$O\
+	sv_game.$O\
+	sv_init.$O\
+	sv_main.$O\
+	sv_send.$O\
+	sv_user.$O\
+	sv_world.$O\
+	cmd.$O\
+	cmodel.$O\
+	common.$O\
+	crc.$O\
+	cvar.$O\
+	files.$O\
+	md4.$O\
+	net_chan.$O\
+	pmove.$O\
+	cd.$O\
+	in.$O\
+	snd.$O\
+	sys.$O\
+	udp.$O\
+	vid.$O\
+	vmenu.$O\
+	r_aclip.$O\
+	r_alias.$O\
+	r_bsp.$O\
+	r_draw.$O\
+	r_edge.$O\
+	r_image.$O\
+	r_light.$O\
+	r_main.$O\
+	r_misc.$O\
+	r_model.$O\
+	r_part.$O\
+	r_poly.$O\
+	r_polyse.$O\
+	r_rast.$O\
+	r_scan.$O\
+	r_sprite.$O\
+	r_surf.$O\
+
+HFILES=\
+	adivtab.h\
+	anorms.h\
+	dat.h\
+	fns.h\
+	rand1k.h\
+
+BIN=$home/bin/$objtype
+</sys/src/cmd/mkone
+
+CFLAGS=-FTVw -I$GAME
+
+$LIB:V:
+	cd $GAME
+	mk
+
+clean nuke:V:
+	@{ cd $GAME; mk $target }
+	rm -f *.[$OS] [$OS].out $TARG
--- /dev/null
+++ b/mkfile.xatrix
@@ -1,0 +1,88 @@
+</$objtype/mkfile
+
+GAME=xatrix
+TARG=q2$GAME
+LIB=$GAME/$GAME.$O.a
+
+OFILES=\
+	cl_cin.$O\
+	cl_ents.$O\
+	cl_fx.$O\
+	cl_newfx.$O\
+	cl_input.$O\
+	cl_inv.$O\
+	cl_main.$O\
+	cl_parse.$O\
+	cl_pred.$O\
+	cl_tent.$O\
+	cl_scrn.$O\
+	cl_view.$O\
+	console.$O\
+	fs.$O\
+	keys.$O\
+	menu.$O\
+	snd_dma.$O\
+	snd_mem.$O\
+	snd_mix.$O\
+	qmenu.$O\
+	sv_ccmds.$O\
+	sv_ents.$O\
+	sv_game.$O\
+	sv_init.$O\
+	sv_main.$O\
+	sv_send.$O\
+	sv_user.$O\
+	sv_world.$O\
+	cmd.$O\
+	cmodel.$O\
+	common.$O\
+	crc.$O\
+	cvar.$O\
+	files.$O\
+	md4.$O\
+	net_chan.$O\
+	pmove.$O\
+	cd.$O\
+	in.$O\
+	snd.$O\
+	sys.$O\
+	udp.$O\
+	vid.$O\
+	vmenu.$O\
+	r_aclip.$O\
+	r_alias.$O\
+	r_bsp.$O\
+	r_draw.$O\
+	r_edge.$O\
+	r_image.$O\
+	r_light.$O\
+	r_main.$O\
+	r_misc.$O\
+	r_model.$O\
+	r_part.$O\
+	r_poly.$O\
+	r_polyse.$O\
+	r_rast.$O\
+	r_scan.$O\
+	r_sprite.$O\
+	r_surf.$O\
+
+HFILES=\
+	adivtab.h\
+	anorms.h\
+	dat.h\
+	fns.h\
+	rand1k.h\
+
+BIN=$home/bin/$objtype
+</sys/src/cmd/mkone
+
+CFLAGS=-FTVw -I$GAME
+
+$LIB:V:
+	cd $GAME
+	mk
+
+clean nuke:V:
+	@{ cd $GAME; mk $target }
+	rm -f *.[$OS] [$OS].out $TARG
--- /dev/null
+++ b/rogue/dm_ball.c
@@ -1,0 +1,678 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+// defines
+
+#define DBALL_GOAL_TEAM1	0x0001
+#define DBALL_GOAL_TEAM2	0x0002
+
+// globals
+
+edict_t *dball_ball_entity = NULL;
+int		dball_ball_startpt_count;
+int		dball_team1_goalscore;
+int		dball_team2_goalscore;
+
+cvar_t	*dball_team1_skin;
+cvar_t	*dball_team2_skin;
+cvar_t	*goallimit;
+
+// prototypes
+
+extern void EndDMLevel (void);
+extern void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+extern void	SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
+extern float PlayersRangeFromSpot (edict_t *spot);
+
+void DBall_BallDie (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void DBall_BallRespawn (edict_t *self);
+
+// **************************
+// Game rules
+// **************************
+
+int DBall_CheckDMRules (void)
+{
+	if(goallimit && goallimit->value)
+	{
+		if(dball_team1_goalscore >= goallimit->value)
+			gi.bprintf (PRINT_HIGH, "Team 1 Wins.\n");
+		else if(dball_team2_goalscore >= goallimit->value)
+			gi.bprintf (PRINT_HIGH, "Team 2 Wins.\n");
+		else
+			return 0;
+
+		EndDMLevel ();
+		return 1;
+	}
+
+	return 0;
+}
+
+//==================
+//==================
+void DBall_ClientBegin (edict_t *ent)
+{
+	int			team1, team2, unassigned;
+	edict_t		*other;
+	char		*p;
+	static char	value[512];
+	int			j;
+
+	team1 = 0;
+	team2 = 0;
+	unassigned = 0;
+
+	for (j = 1; j <= game.maxclients; j++)
+	{
+		other = &g_edicts[j];
+		if (!other->inuse)
+			continue;
+		if (!other->client)
+			continue;
+		if (other == ent)	// don't count the new player
+			continue;
+		
+		strcpy(value, Info_ValueForKey (other->client->pers.userinfo, "skin"));
+		p = strchr(value, '/');
+		if (p)
+		{
+			if(!strcmp(dball_team1_skin->string, value))
+				team1++;
+			else if(!strcmp(dball_team2_skin->string, value))
+				team2++;
+			else
+				unassigned++;
+		}
+		else
+			unassigned++;
+	}
+
+	if(team1 > team2)
+	{
+		gi.dprintf("assigned to team 2\n");
+		Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
+	}
+	else
+	{
+		gi.dprintf("assigned to team 1\n");
+		Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
+	}
+	
+	ClientUserinfoChanged(ent, ent->client->pers.userinfo);
+
+	if(unassigned)
+		gi.dprintf("%d unassigned players present!\n", unassigned);
+}
+
+//==================
+//==================
+void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
+{
+	edict_t	*bestspot;
+	float	bestdistance, bestplayerdistance;
+	edict_t	*spot;
+	char	*spottype;
+	char	skin[512];
+
+	strcpy(skin, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
+	if(!strcmp(dball_team1_skin->string, skin))
+		spottype = "dm_dball_team1_start";
+	else if(!strcmp(dball_team2_skin->string, skin))
+		spottype = "dm_dball_team2_start";
+	else
+		spottype = "info_player_deathmatch";
+
+	spot = NULL;
+	bestspot = NULL;
+	bestdistance = 0;
+	while ((spot = G_Find (spot, FOFS(classname), spottype)) != NULL)
+	{
+		bestplayerdistance = PlayersRangeFromSpot (spot);
+
+		if (bestplayerdistance > bestdistance)
+		{
+			bestspot = spot;
+			bestdistance = bestplayerdistance;
+		}
+	}
+
+	if (bestspot)
+	{
+		VectorCopy (bestspot->s.origin, origin);
+		origin[2] += 9;
+		VectorCopy (bestspot->s.angles, angles);
+		return;
+	}
+
+	// if we didn't find an appropriate spawnpoint, just
+	// call the standard one.
+	SelectSpawnPoint(ent, origin, angles);
+}
+
+//==================
+//==================
+void DBall_GameInit (void)
+{
+	// we don't want a minimum speed for friction to take effect.
+	// this will allow any knockback to move stuff.
+	sv_stopspeed->value = 0;
+	dball_team1_goalscore = 0;
+	dball_team2_goalscore = 0;
+
+	dmflags->value = (int)dmflags->value | DF_NO_MINES | DF_NO_NUKES | DF_NO_STACK_DOUBLE | 
+						DF_NO_FRIENDLY_FIRE | DF_SKINTEAMS;
+	
+	dball_team1_skin = gi.cvar ("dball_team1_skin", "male/ctf_r", 0);
+	dball_team2_skin = gi.cvar ("dball_team2_skin", "male/ctf_b", 0);
+	goallimit = gi.cvar ("goallimit", "0", 0);
+}
+
+//==================
+//==================
+void DBall_PostInitSetup (void)
+{
+	edict_t		*e;
+
+	e=NULL;
+	// turn teleporter destinations nonsolid.
+	while(e = G_Find (e, FOFS(classname), "misc_teleporter_dest"))
+	{
+		e->solid = SOLID_NOT;
+		gi.linkentity (e);
+	}
+
+	// count the ball start points
+	dball_ball_startpt_count = 0;
+	e=NULL;
+	while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
+	{
+		dball_ball_startpt_count++;
+	}
+
+	if(dball_ball_startpt_count == 0)
+		gi.dprintf("No Deathball start points!\n");
+}
+
+//==================
+// DBall_ChangeDamage - half damage between players. full if it involves
+//		the ball entity
+//==================
+int DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int)
+{
+	// cut player -> ball damage to 1
+	if (targ == dball_ball_entity)
+		return 1;
+
+	// damage player -> player is halved
+	if (attacker != dball_ball_entity)
+		return damage / 2;
+
+	return damage;
+}
+
+//==================
+//==================
+int DBall_ChangeKnockback (edict_t *targ, edict_t *, int knockback, int mod)
+{
+	if(targ != dball_ball_entity)
+		return knockback;
+	
+	if(knockback < 1)
+	{
+		// FIXME - these don't account for quad/double
+		if(mod == MOD_ROCKET)		// rocket
+			knockback = 70;
+		else if(mod == MOD_BFG_EFFECT)	// bfg
+			knockback = 90;
+		else
+	 		gi.dprintf ("zero knockback, mod %d\n", mod);
+	}
+	else
+	{
+		// FIXME - change this to an array?
+		switch(mod)
+		{
+			case MOD_BLASTER:
+				knockback *= 3;
+				break;
+			case MOD_SHOTGUN:
+				knockback = (knockback * 3) / 8;
+				break;
+			case MOD_SSHOTGUN:
+				knockback = knockback / 3;
+				break;
+			case MOD_MACHINEGUN:
+				knockback = (knockback * 3) / 2;
+				break;
+			case MOD_HYPERBLASTER:
+				knockback *= 4;
+				break;
+			case MOD_GRENADE:
+			case MOD_HANDGRENADE:
+			case MOD_PROX:
+			case MOD_G_SPLASH:
+			case MOD_HG_SPLASH:
+			case MOD_HELD_GRENADE:
+			case MOD_TRACKER:
+			case MOD_DISINTEGRATOR:
+				knockback /= 2;
+				break;
+			case MOD_R_SPLASH:
+				knockback = (knockback * 3) / 2;
+				break;
+			case MOD_RAILGUN:
+			case MOD_HEATBEAM:
+				knockback /= 3;
+				break;
+		}
+	}
+
+//	gi.dprintf("mod: %d    knockback: %d\n", mod, knockback);
+	return knockback;
+}
+
+// **************************
+// Goals
+// **************************
+
+void DBall_GoalTouch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	int		team_score;
+	int		scorechange;
+	int		j;
+	char	value[512];
+	char	*p;
+	edict_t	*ent;
+
+	if(other != dball_ball_entity)
+		return;
+
+	self->health = self->max_health;
+
+	// determine which team scored, and bump the team score
+	if(self->spawnflags & DBALL_GOAL_TEAM1)
+	{
+		dball_team1_goalscore += self->wait;		
+		team_score = 1;
+	}
+	else
+	{
+		dball_team2_goalscore += self->wait;		
+		team_score = 2;
+	}
+
+	// bump the score for everyone on the correct team.
+	for (j = 1; j <= game.maxclients; j++)
+	{
+		ent = &g_edicts[j];
+		if (!ent->inuse)
+			continue;
+		if (!ent->client)
+			continue;
+		
+		if (ent == other->enemy)
+			scorechange = self->wait + 5;
+		else
+			scorechange = self->wait;
+
+		strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
+		p = strchr(value, '/');
+		if (p)
+		{
+			if(!strcmp(dball_team1_skin->string, value))
+			{
+				if(team_score == 1)
+					ent->client->resp.score += scorechange;
+				else if(other->enemy == ent)
+					ent->client->resp.score -= scorechange;
+			}
+			else if(!strcmp(dball_team2_skin->string, value))
+			{
+				if(team_score == 2)
+					ent->client->resp.score += scorechange;
+				else if(other->enemy == ent)
+					ent->client->resp.score -= scorechange;
+			}
+			else
+				gi.dprintf("unassigned player!!!!\n");
+		}
+	}
+
+	if(other->enemy)
+		gi.dprintf("score for team %d by %s\n", team_score, other->enemy->client->pers.netname);
+	else
+		gi.dprintf("score for team %d by someone\n", team_score);
+
+	DBall_BallDie (other, other->enemy, other->enemy, 0, vec3_origin);
+
+	G_UseTargets (self, other);
+}
+
+// **************************
+// Ball
+// **************************
+
+edict_t *PickBallStart (edict_t *)
+{
+	int		which, current;
+	edict_t	*e;
+
+	which = ceil(qrandom() * dball_ball_startpt_count);
+	e = NULL;
+	current = 0;
+
+	while(e = G_Find (e, FOFS(classname), "dm_dball_ball_start"))
+	{
+		current++;
+		if(current == which)
+			return e;
+	}
+
+	if(current == 0)
+		gi.dprintf("No ball start points found!\n");
+
+	return G_Find(NULL, FOFS(classname), "dm_dball_ball_start");
+}
+
+//==================
+// DBall_BallTouch - if the ball hit another player, hurt them
+//==================
+void DBall_BallTouch (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t	dir;
+	float	dot;
+	float	speed;
+
+	if(other->takedamage == DAMAGE_NO)
+		return;
+
+	// hit a player
+	if(other->client) 
+	{
+		if(ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
+		{
+			speed = VectorLength(ent->velocity);
+
+			VectorSubtract(ent->s.origin, other->s.origin, dir);
+			dot = DotProduct(dir, ent->velocity);
+
+			if(dot > 0.7)
+			{
+				T_Damage (other, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 
+						speed/10, speed/10, 0, MOD_DBALL_CRUSH);
+			}
+		}
+	}
+}
+
+//==================
+// DBall_BallPain
+//==================
+void DBall_BallPain (edict_t *self, edict_t *other, float, int)
+{
+	self->enemy = other;
+	self->health = self->max_health;
+//	if(other->classname)
+//		gi.dprintf("hurt by %s -- %d\n", other->classname, self->health);
+}
+
+void DBall_BallDie (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	// do the splash effect
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DBALL_GOAL);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	VectorClear(self->s.angles);
+	VectorClear(self->velocity);
+	VectorClear(self->avelocity);
+
+	// make it invisible and desolid until respawn time
+	self->solid = SOLID_NOT;
+//	self->s.modelindex = 0;
+	self->think = DBall_BallRespawn;
+	self->nextthink = level.time + 2;
+	gi.linkentity(self);
+
+}
+
+void DBall_BallRespawn (edict_t *self)
+{
+	edict_t		*start;
+
+	// do the splash effect
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DBALL_GOAL);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	// move the ball and stop it
+	start = PickBallStart(self);
+	if(start)
+	{
+		VectorCopy(start->s.origin, self->s.origin);
+		VectorCopy(start->s.origin, self->s.old_origin);
+	}
+
+	VectorClear(self->s.angles);
+	VectorClear(self->velocity);
+	VectorClear(self->avelocity);
+	
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
+	self->s.event = EV_PLAYER_TELEPORT;
+	self->groundentity = NULL;
+
+	// kill anything at the destination
+	KillBox (self);
+
+	gi.linkentity (self);
+}
+
+// ************************
+// SPEED CHANGES
+// ************************
+
+#define DBALL_SPEED_ONEWAY		1
+
+void DBall_SpeedTouch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	float	dot;
+	vec3_t	vel;
+
+	if(other != dball_ball_entity)
+		return;
+
+	if(self->timestamp >= level.time)
+		return;
+
+	if(VectorLength(other->velocity) < 1)
+		return;
+
+	if(self->spawnflags & DBALL_SPEED_ONEWAY)
+	{
+		VectorCopy (other->velocity, vel);
+		VectorNormalize (vel);
+		dot = DotProduct (vel, self->movedir);
+		if(dot < 0.8)
+			return;
+	}
+
+	self->timestamp = level.time + self->delay;
+	VectorScale (other->velocity, self->speed, other->velocity);
+}
+
+// ************************
+// SPAWN FUNCTIONS
+// ************************
+
+/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48)
+Deathball Ball
+*/
+void SP_dm_dball_ball (edict_t *self)
+{
+	if(!(deathmatch->value))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	dball_ball_entity = self;
+//	VectorCopy (self->s.origin, dball_ball_startpt);
+
+	self->s.modelindex = gi.modelindex ("models/objects/dball/tris.md2");
+	VectorSet (self->mins, -32, -32, -32);
+	VectorSet (self->maxs, 32, 32, 32);
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_NEWTOSS;
+	self->clipmask = MASK_MONSTERSOLID;
+
+	self->takedamage = DAMAGE_YES;
+	self->mass = 50;
+	self->health = 50000;
+	self->max_health = 50000;
+	self->pain = DBall_BallPain;
+	self->die = DBall_BallDie;
+	self->touch = DBall_BallTouch;
+
+	gi.linkentity (self);
+}
+
+/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
+Deathball team 1 start point
+*/
+void SP_dm_dball_team1_start (edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+}
+
+/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
+Deathball team 2 start point
+*/
+void SP_dm_dball_team2_start (edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+}
+
+/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
+Deathball ball start point
+*/
+void SP_dm_dball_ball_start (edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+}
+
+/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
+Deathball ball speed changing field.
+
+speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
+angle: used with ONEWAY so speed change is only one way.
+delay: time between speed changes (default: 0.2 sec)
+*/
+void SP_dm_dball_speed_change (edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	if(!self->speed)
+		self->speed = 2;
+
+	if(!self->delay)
+		self->delay = 0.2;
+
+	self->touch = DBall_SpeedTouch;
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+
+	if (!VectorCompare(self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+	else
+		VectorSet (self->movedir, 1, 0, 0);
+
+	gi.setmodel (self, self->model);
+	gi.linkentity (self);
+}
+
+/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
+Deathball goal
+
+Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
+
+"wait": score to be given for this goal (default 10) player gets score+5.
+*/
+void SP_dm_dball_goal (edict_t *self)
+{
+	if(!(deathmatch->value))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	if(gamerules && (gamerules->value != RDM_DEATHBALL))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	if(!self->wait)
+		self->wait = 10;
+
+	self->touch = DBall_GoalTouch;
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+
+	if (!VectorCompare(self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	gi.setmodel (self, self->model);
+	gi.linkentity (self);
+
+}
--- /dev/null
+++ b/rogue/dm_tag.c
@@ -1,0 +1,333 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+extern edict_t *SelectFarthestDeathmatchSpawnPoint (void);
+extern void	SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
+
+void SP_dm_tag_token (edict_t *self);
+
+// ***********************
+// Tag Specific Stuff
+// ***********************
+
+edict_t		*tag_token;
+edict_t		*tag_owner;
+int			tag_count;
+
+//=================
+//=================
+void Tag_PlayerDeath(edict_t *targ, edict_t *, edict_t *)
+{
+//	gi.dprintf("%s killed %s\n", attacker->classname, targ->classname);
+//	gi.dprintf("%x killed %x\n", attacker, targ);
+
+	if(tag_token && targ && (targ == tag_owner))
+	{
+//		gi.dprintf("owner died/suicided. dropping\n");
+		Tag_DropToken(targ, FindItem( "Tag Token" ));
+		tag_owner = NULL;
+		tag_count = 0;
+	}
+//	else
+//		gi.dprintf("unrelated slaying\n");
+}
+
+//=================
+//=================
+void Tag_KillItBonus (edict_t *self)
+{
+	edict_t			*armor;
+
+	// if the player is hurt, boost them up to max.
+	if(self->health < self->max_health)
+	{
+		self->health += 200;
+		if(self->health > self->max_health)
+			self->health = self->max_health;
+	}
+
+	// give the player a body armor
+	armor = G_Spawn();
+	armor->spawnflags |= DROPPED_ITEM;
+	armor->item = FindItem("Body Armor");
+	Touch_Item(armor, self, NULL, NULL);
+	if(armor->inuse)
+		G_FreeEdict(armor);
+}
+
+//=================
+//=================
+void Tag_PlayerDisconnect (edict_t *self)
+{
+	if(tag_token && self && (self == tag_owner))
+	{
+//		gi.dprintf("owner died/suicided. dropping\n");
+		Tag_DropToken(self, FindItem( "Tag Token" ));
+		tag_owner = NULL;
+		tag_count = 0;
+	}
+}
+
+//=================
+//=================
+void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange)
+{
+	gitem_t		*quad;
+	int			mod;
+
+	mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+
+//	gi.dprintf("%s killed %s\n", attacker->classname, victim->classname);
+//	gi.dprintf("%x killed %x\n", attacker, victim);
+	if(tag_token && tag_owner)
+	{
+		// owner killed somone else
+		if((scoreChange > 0) && tag_owner == attacker)
+		{
+			scoreChange = 3;
+			tag_count++;
+//			gi.dprintf("tag total: %d\n", tag_count);
+			if(tag_count == 5)
+			{
+//				gi.dprintf("going to quad\n");
+				quad = FindItem ("Quad Damage");
+				attacker->client->pers.inventory[ITEM_INDEX(quad)]++;
+				quad->use (attacker, quad);
+				tag_count = 0;
+			}
+		}
+		// owner got killed. 5 points and switch owners
+		else if(tag_owner == victim && tag_owner != attacker)
+		{
+//			gi.dprintf("owner killed by another player.\n");
+			scoreChange = 5;
+			if ((mod == MOD_HUNTER_SPHERE) || (mod == MOD_DOPPLE_EXPLODE) ||
+				(mod == MOD_DOPPLE_VENGEANCE) || (mod == MOD_DOPPLE_HUNTER) ||
+				(attacker->health <= 0))
+			{
+				Tag_DropToken(tag_owner, FindItem( "Tag Token" ));
+				tag_owner = NULL;
+				tag_count = 0;
+			}
+			else
+			{
+				Tag_KillItBonus(attacker);
+				tag_owner = attacker;
+				tag_count = 0;
+			}
+		}
+//		else
+//			gi.dprintf("unaffected slaying\n");
+	}
+//	else
+//		gi.dprintf("no tag token?\n");
+
+	attacker->client->resp.score += scoreChange;
+}
+
+//=================
+//=================
+qboolean Tag_PickupToken (edict_t *ent, edict_t *other)
+{
+	if(gamerules && (gamerules->value != 2))
+	{
+		return false;
+	}
+
+//	gi.dprintf("tag token picked up by %x\n", other);
+	// sanity checking is good.
+	if(tag_token != ent)
+		tag_token = ent;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+	
+	tag_owner = other;
+	tag_count = 0;
+
+	Tag_KillItBonus (other);
+
+	return true;
+}
+
+//=================
+//=================
+void Tag_Respawn (edict_t *ent)
+{
+	edict_t	*spot;
+
+	spot = SelectFarthestDeathmatchSpawnPoint();
+	if(spot == NULL)
+	{
+//		gi.dprintf("No open spawn point, waiting...\n");
+		ent->nextthink = level.time + 1;
+		return;
+	}
+
+//	gi.dprintf("Relocating\n");
+
+	VectorCopy(spot->s.origin, ent->s.origin);
+	gi.linkentity(ent);
+}
+
+//=================
+//=================
+void Tag_MakeTouchable (edict_t *ent)
+{
+	ent->touch = Touch_Item;
+
+	tag_token->think = Tag_Respawn;
+
+	// check here to see if it's in lava or slime. if so, do a respawn sooner
+	if(gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME))
+	{
+//		gi.dprintf("spawned in slime or lava. quick relocate\n");
+		tag_token->nextthink = level.time + 3;
+	}
+	else
+	{
+//		gi.dprintf("spawned in the clear. regular relocate\n");
+		tag_token->nextthink = level.time + 30;
+	}
+}
+
+//=================
+//=================
+void Tag_DropToken (edict_t *ent, gitem_t *item)
+{
+	trace_t	trace;
+	vec3_t	forward, right;
+	vec3_t	offset;
+
+//	if(ent->client)
+//		gi.dprintf("%s dropped the tag token\n", ent->client->pers.netname);
+//	else
+//		gi.dprintf("non-client dropped the tag token  (%s)\n", ent->classname);
+
+	// reset the score count for next player
+	tag_count = 0;
+	tag_owner = NULL;
+
+	tag_token = G_Spawn();
+
+	tag_token->classname = item->classname;
+	tag_token->item = item;
+	tag_token->spawnflags = DROPPED_ITEM;
+	tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL;
+	tag_token->s.renderfx = RF_GLOW;
+	VectorSet (tag_token->mins, -15, -15, -15);
+	VectorSet (tag_token->maxs, 15, 15, 15);
+	gi.setmodel (tag_token, tag_token->item->world_model);
+	tag_token->solid = SOLID_TRIGGER;
+	tag_token->movetype = MOVETYPE_TOSS;  
+	tag_token->touch = NULL;
+	tag_token->owner = ent;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	VectorSet(offset, 24, 0, -16);
+	G_ProjectSource (ent->s.origin, offset, forward, right, tag_token->s.origin);
+	trace = gi.trace (ent->s.origin, tag_token->mins, tag_token->maxs,
+		tag_token->s.origin, ent, CONTENTS_SOLID);
+	VectorCopy (trace.endpos, tag_token->s.origin);
+
+	VectorScale (forward, 100, tag_token->velocity);
+	tag_token->velocity[2] = 300;
+
+	tag_token->think = Tag_MakeTouchable;
+	tag_token->nextthink = level.time + 1;
+
+	gi.linkentity (tag_token);
+
+//	tag_token = Drop_Item (ent, item);
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+}
+
+//=================
+//=================
+void Tag_PlayerEffects (edict_t *ent)
+{
+	if(ent == tag_owner)
+		ent->s.effects |= EF_TAGTRAIL;
+}
+
+//=================
+//=================
+void Tag_DogTag (edict_t *ent, edict_t *, char **pic)
+{
+	if(ent == tag_owner)
+			(*pic)="tag3";
+}
+
+//=================
+// Tag_ChangeDamage - damage done that does not involve the tag owner
+//		is at 75% original to encourage folks to go after the tag owner.
+//=================
+int	Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int)
+{
+	if((targ != tag_owner) && (attacker != tag_owner))
+		return (damage * 3 / 4);
+
+	return damage;
+}
+
+//=================
+//=================
+void Tag_GameInit (void)
+{
+	tag_token = NULL;
+	tag_owner = NULL;
+	tag_count = 0;
+}
+
+//=================
+//=================
+void Tag_PostInitSetup (void)
+{
+	edict_t		*e;
+	vec3_t		origin, angles;
+
+	// automatic spawning of tag token if one is not present on map.
+	e = G_Find (NULL, FOFS(classname), "dm_tag_token");
+	if(e == NULL)
+	{
+		e = G_Spawn();
+		e->classname = "dm_tag_token";
+
+		SelectSpawnPoint (e, origin, angles);
+		VectorCopy(origin, e->s.origin);
+		VectorCopy(origin, e->s.old_origin);
+		VectorCopy(angles, e->s.angles);
+		SP_dm_tag_token (e);
+	}
+}
+
+/*QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16)
+The tag token for deathmatch tag games.
+*/
+void SP_dm_tag_token (edict_t *self)
+{
+	if(!(deathmatch->value))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	if(gamerules && (gamerules->value != 2))
+	{
+		G_FreeEdict(self);	
+		return;
+	}
+
+	// store the tag token edict pointer for later use.
+	tag_token = self;
+	tag_count = 0;
+
+	self->classname = "dm_tag_token";
+	self->model = "models/items/tagtoken/tris.md2";
+	self->count = 1;
+	SpawnItem (self, FindItem ("Tag Token"));
+}
+
--- /dev/null
+++ b/rogue/g_ai.c
@@ -1,0 +1,1598 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+qboolean FindTarget (edict_t *self);
+extern cvar_t	*maxclients;
+
+qboolean ai_checkattack (edict_t *self, float dist);
+
+qboolean	enemy_vis;
+qboolean	enemy_infront;
+int			enemy_range;
+float		enemy_yaw;
+
+// ROGUE STUFF
+#define SLIDING_TROOPS	1
+#define	MAX_SIDESTEP	8.0
+//
+
+//============================================================================
+
+
+/*
+=================
+AI_SetSightClient
+
+Called once each frame to set level.sight_client to the
+player to be checked for in findtarget.
+
+If all clients are either dead or in notarget, sight_client
+will be null.
+
+In coop games, sight_client will cycle between the clients.
+=================
+*/
+void AI_SetSightClient (void)
+{
+	edict_t	*ent;
+	int		start, check;
+
+	if (level.sight_client == NULL)
+		start = 1;
+	else
+		start = level.sight_client - g_edicts;
+
+	check = start;
+	while (1)
+	{
+		check++;
+		if (check > game.maxclients)
+			check = 1;
+		ent = &g_edicts[check];
+		if (ent->inuse
+			&& ent->health > 0
+			&& !(ent->flags & (FL_NOTARGET|FL_DISGUISED)) )
+		{
+			level.sight_client = ent;
+			return;		// got one
+		}
+		if (check == start)
+		{
+			level.sight_client = NULL;
+			return;		// nobody to see
+		}
+	}
+}
+
+//============================================================================
+
+/*
+=============
+ai_move
+
+Move the specified distance at current facing.
+This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
+==============
+*/
+void ai_move (edict_t *self, float dist)
+{
+	M_walkmove (self, self->s.angles[YAW], dist);
+}
+
+
+/*
+=============
+ai_stand
+
+Used for standing around and looking for players
+Distance is for slight position adjustments needed by the animations
+==============
+*/
+void ai_stand (edict_t *self, float dist)
+{
+	vec3_t	v;
+	// PMM
+	qboolean retval;
+
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		if (self->enemy)
+		{
+			VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+			self->ideal_yaw = vectoyaw(v);
+			if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+			{
+				self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+				self->monsterinfo.run (self);
+			}
+			if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+				M_ChangeYaw (self);
+			// PMM
+			// find out if we're going to be shooting
+			retval = ai_checkattack (self, 0);
+			// record sightings of player
+			if ((self->enemy) && (self->enemy->inuse) && (visible(self, self->enemy)))
+			{
+				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+				VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+				VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+				self->monsterinfo.trail_time = level.time;
+				self->monsterinfo.blind_fire_delay = 0;
+			}
+			// check retval to make sure we're not blindfiring
+			else if (!retval)
+			{
+				FindTarget (self);
+				return;
+			}
+//			ai_checkattack (self, 0);
+			// pmm
+		}
+		else
+			FindTarget (self);
+		return;
+	}
+
+	if (FindTarget (self))
+		return;
+	
+	if (level.time > self->monsterinfo.pausetime)
+	{
+		self->monsterinfo.walk (self);
+		return;
+	}
+
+	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.idle (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_walk
+
+The monster is walking it's beat
+=============
+*/
+void ai_walk (edict_t *self, float dist)
+{
+	M_MoveToGoal (self, dist);
+
+	// check for noticing a player
+	if (FindTarget (self))
+		return;
+
+	if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.search (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_charge
+
+Turns towards target and advances
+Use this call with a distance of 0 to replace ai_face
+==============
+*/
+void ai_charge (edict_t *self, float dist)
+{
+	vec3_t	v;
+	// PMM
+	float	ofs;
+	// PMM
+
+	// PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but
+	// don't set the ideal_yaw
+
+	// This is put in there so monsters won't move towards the origin after killing
+	// a tesla. This could be problematic, so keep an eye on it.
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	// PMM - save blindfire target
+	if (visible(self, self->enemy))
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+	// pmm 
+
+	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+	{
+		VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+		self->ideal_yaw = vectoyaw(v);
+//		gi.dprintf ("enemy = %s\n", vtos (self->enemy->s.origin));
+//		gi.dprintf ("enemy: ideal yaw is %f\n", self->ideal_yaw);
+	}
+//	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+	M_ChangeYaw (self);
+// PMM
+//	if (dist)
+//		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (dist)
+	{
+		if (self->monsterinfo.aiflags & AI_CHARGING)
+		{
+			M_MoveToGoal (self, dist);
+			return;
+		}
+		// circle strafe support
+		if (self->monsterinfo.attack_state == AS_SLIDING)
+		{
+			// if we're fighting a tesla, NEVER circle strafe
+			if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla")))
+				ofs = 0;
+			else if (self->monsterinfo.lefty)
+				ofs = 90;
+			else
+				ofs = -90;
+			
+			if (M_walkmove (self, self->ideal_yaw + ofs, dist))
+				return;
+				
+			self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+			M_walkmove (self, self->ideal_yaw - ofs, dist);
+		}
+		else
+			M_walkmove (self, self->s.angles[YAW], dist);
+	}
+// PMM
+}
+
+
+/*
+=============
+ai_turn
+
+don't move, but turn towards ideal_yaw
+Distance is for slight position adjustments needed by the animations
+=============
+*/
+void ai_turn (edict_t *self, float dist)
+{
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (FindTarget (self))
+		return;
+	
+	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+		M_ChangeYaw (self);
+}
+
+
+/*
+
+.enemy
+Will be world if not currently angry at anyone.
+
+.movetarget
+The next path spot to walk toward.  If .enemy, ignore .movetarget.
+When an enemy is killed, the monster will try to return to it's path.
+
+.hunt_time
+Set to time + something when the player is in sight, but movement straight for
+him is blocked.  This causes the monster to use wall following code for
+movement direction instead of sighting on the player.
+
+.ideal_yaw
+A yaw angle of the intended direction, which will be turned towards at up
+to 45 deg / state.  If the enemy is in view and hunt_time is not active,
+this will be the exact line towards the enemy.
+
+.pausetime
+A monster will leave it's stand state and head towards it's .movetarget when
+time > .pausetime.
+
+walkmove(angle, speed) primitive is all or nothing
+*/
+
+/*
+=============
+range
+
+returns the range catagorization of an entity reletive to self
+0	melee range, will become hostile even if back is turned
+1	visibility and infront, or visibility and show hostile
+2	infront and show hostile
+3	only triggered by damage
+=============
+*/
+int range (edict_t *self, edict_t *other)
+{
+	vec3_t	v;
+	float	len;
+
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	len = VectorLength (v);
+	if (len < MELEE_DISTANCE)
+		return RANGE_MELEE;
+	if (len < 500)
+		return RANGE_NEAR;
+	if (len < 1000)
+		return RANGE_MID;
+	return RANGE_FAR;
+}
+
+/*
+=============
+visible
+
+returns 1 if the entity is visible to self, even if not infront ()
+=============
+*/
+qboolean visible (edict_t *self, edict_t *other)
+{
+	vec3_t	spot1;
+	vec3_t	spot2;
+	trace_t	trace;
+
+	VectorCopy (self->s.origin, spot1);
+	spot1[2] += self->viewheight;
+	VectorCopy (other->s.origin, spot2);
+	spot2[2] += other->viewheight;
+	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
+	
+	if (trace.fraction == 1.0 || trace.ent == other)		// PGM
+		return true;
+	return false;
+}
+
+
+/*
+=============
+infront
+
+returns 1 if the entity is in front (in sight) of self
+=============
+*/
+qboolean infront (edict_t *self, edict_t *other)
+{
+	vec3_t	vec;
+	float	dot;
+	vec3_t	forward;
+	
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	dot = DotProduct (vec, forward);
+	
+	if (dot > 0.3)
+		return true;
+	return false;
+}
+
+
+//============================================================================
+
+void HuntTarget (edict_t *self)
+{
+	vec3_t	vec;
+
+	self->goalentity = self->enemy;
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.stand (self);
+	else
+		self->monsterinfo.run (self);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	self->ideal_yaw = vectoyaw(vec);
+	// wait a while before first attack
+	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+		AttackFinished (self, 1);
+}
+
+void FoundTarget (edict_t *self)
+{
+	// let other monsters see this monster for a while
+	if (self->enemy->client)
+	{
+		if(self->enemy->flags & FL_DISGUISED)
+		{
+//			level.disguise_violator = self->enemy;
+//			level.disguise_violation_framenum = level.framenum + 5;
+			self->enemy->flags &= ~FL_DISGUISED;
+		}
+		
+		level.sight_entity = self;
+		level.sight_entity_framenum = level.framenum;
+		level.sight_entity->light_level = 128;
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+	self->monsterinfo.trail_time = level.time;
+	// PMM
+	VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+	self->monsterinfo.blind_fire_delay = 0;
+	// PMM
+
+	if (!self->combattarget)
+	{
+		HuntTarget (self);
+		return;
+	}
+
+	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
+	if (!self->movetarget)
+	{
+		self->goalentity = self->movetarget = self->enemy;
+		HuntTarget (self);
+		gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
+		return;
+	}
+
+	// clear out our combattarget, these are a one shot deal
+	self->combattarget = NULL;
+	self->monsterinfo.aiflags |= AI_COMBAT_POINT;
+
+	// clear the targetname, that point is ours!
+	self->movetarget->targetname = NULL;
+	self->monsterinfo.pausetime = 0;
+
+	// run for it
+	self->monsterinfo.run (self);
+}
+
+
+/*
+===========
+FindTarget
+
+Self is currently not attacking anything, so try to find a target
+
+Returns TRUE if an enemy was sighted
+
+When a player fires a missile, the point of impact becomes a fakeplayer so
+that monsters that see the impact will respond as if they had seen the
+player.
+
+To avoid spending too much time, only a single client (or fakeclient) is
+checked each frame.  This means multi player games will have slightly
+slower noticing monsters.
+============
+*/
+qboolean FindTarget (edict_t *self)
+{
+	edict_t		*client;
+	qboolean	heardit;
+	int			r;
+
+	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
+		{
+			if (strcmp(self->goalentity->classname, "target_actor") == 0)
+				return false;
+		}
+
+		//FIXME look for monsters?
+		return false;
+	}
+
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+		return false;
+
+// if the first spawnflag bit is set, the monster will only wake up on
+// really seeing the player, not another monster getting angry or hearing
+// something
+
+// revised behavior so they will wake up if they "see" a player make a noise
+// but not weapon impact/explosion noises
+
+	heardit = false;
+	if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sight_entity;
+		if (client->enemy == self->enemy)
+		{
+			return false;
+		}
+	}
+	else if (level.disguise_violation_framenum > level.framenum)
+	{
+		client = level.disguise_violator;
+	}
+	else if (level.sound_entity_framenum >= (level.framenum - 1))
+	{
+		client = level.sound_entity;
+		heardit = true;
+	}
+	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sound2_entity;
+		heardit = true;
+	}
+	else
+	{
+		client = level.sight_client;
+		if (!client)
+			return false;	// no clients to get mad at
+	}
+
+	// if the entity went away, forget it
+	if (!client->inuse)
+		return false;
+
+	if (client == self->enemy)
+		return true;	// JDC false;
+
+	//PMM - hintpath coop fix
+	if ((self->monsterinfo.aiflags & AI_HINT_PATH) && (coop) && (coop->value))
+	{
+//		if ((heardit) && (g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("ignoring coop sound target\n");
+		heardit = false;
+	}
+	// pmm
+
+	if (client->client)
+	{
+		if (client->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (client->svflags & SVF_MONSTER)
+	{
+		if (!client->enemy)
+			return false;
+		if (client->enemy->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (heardit)
+	{
+		// pgm - a little more paranoia won't hurt....
+		if ((client->owner) && (client->owner->flags & FL_NOTARGET))
+			return false;
+	}
+	else
+		return false;
+
+	if (!heardit)
+	{
+		r = range (self, client);
+
+		if (r == RANGE_FAR)
+			return false;
+
+// this is where we would check invisibility
+
+		// is client in an spot too dark to be seen?
+		if (client->light_level <= 5)
+			return false;
+
+		if (!visible (self, client))
+		{
+			return false;
+		}
+
+		if (r == RANGE_NEAR)
+		{
+			if (client->show_hostile < level.time && !infront (self, client))
+			{
+				return false;
+			}
+		}
+		else if (r == RANGE_MID)
+		{
+			if (!infront (self, client))
+			{
+				return false;
+			}
+		}
+
+		self->enemy = client;
+
+		if (strcmp(self->enemy->classname, "player_noise") != 0)
+		{
+			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+			if (!self->enemy->client)
+			{
+				self->enemy = self->enemy->enemy;
+				if (!self->enemy->client)
+				{
+					self->enemy = NULL;
+					return false;
+				}
+			}
+		}
+	}
+	else	// heardit
+	{
+		vec3_t	temp;
+
+		if (self->spawnflags & 1)
+		{
+			if (!visible (self, client))
+				return false;
+		}
+		else
+		{
+			if (!gi.inPHS(self->s.origin, client->s.origin))
+				return false;
+		}
+
+		VectorSubtract (client->s.origin, self->s.origin, temp);
+
+		if (VectorLength(temp) > 1000)	// too far to hear
+		{
+			return false;
+		}
+
+		// check area portals - if they are different and not connected then we can't hear it
+		if (client->areanum != self->areanum)
+			if (!gi.AreasConnected(self->areanum, client->areanum))
+				return false;
+	
+		self->ideal_yaw = vectoyaw(temp);
+		if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+			M_ChangeYaw (self);
+
+		// hunt the sound for a bit; hopefully find the real player
+		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
+		self->enemy = client;
+	}
+
+//
+// got one
+//
+	// PMM - if we got an enemy, we need to bail out of hint paths, so take over here
+	if (self->monsterinfo.aiflags & AI_HINT_PATH)
+	{
+//		if(g_showlogic && g_showlogic->value)
+//			gi.dprintf("stopped following hint paths in FindTarget\n");
+
+		// this calls foundtarget for us
+		hintpath_stop (self);
+	}
+	else
+	{
+		FoundTarget (self);
+	}
+	// pmm
+	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
+		self->monsterinfo.sight (self, self->enemy);
+
+	return true;
+}
+
+
+//=============================================================================
+
+/*
+============
+FacingIdeal
+
+============
+*/
+qboolean FacingIdeal(edict_t *self)
+{
+	float	delta;
+
+	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
+	if (delta > 45 && delta < 315)
+		return false;
+	return true;
+}
+
+
+//=============================================================================
+
+qboolean M_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	float	chance;
+	trace_t	tr;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+			{
+				// PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
+				if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
+				{
+					if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 20.0))
+					{
+						if (level.time < self->monsterinfo.attack_finished)
+						{
+							return false;
+						}
+						if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
+						{
+							// wait for our time
+							return false;
+						}
+						else
+						{
+//	gi.WriteByte (svc_temp_entity);
+//	gi.WriteByte (TE_DEBUGTRAIL);
+//	gi.WritePosition (spot1);
+//	gi.WritePosition (self->monsterinfo.blind_fire_target);
+//	gi.multicast (self->s.origin, MULTICAST_ALL);
+							// make sure we're not going to shoot a monster
+							tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
+							if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
+							{
+//								if ((g_showlogic) && (g_showlogic->value))
+//									gi.dprintf ("blindfire blocked\n");
+								return false;
+							}
+
+							self->monsterinfo.attack_state = AS_BLIND;
+							return true;
+						}
+					}
+				}
+				// pmm
+				return false;
+			}
+		}
+	}
+	
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		// don't always melee in easy mode
+		if (skill->value == 0 && (rand()&3) )
+		{
+			// PMM - fix for melee only monsters & strafing
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+			return false;
+		}
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+	{
+		// PMM - fix for melee only monsters & strafing
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+		return false;
+	}
+	
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.2;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.1;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.02;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (skill->value == 0)
+		chance *= 0.5;
+	else if (skill->value >= 2)
+		chance *= 2;
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	if ((qrandom () < chance) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	// PMM -daedalus should strafe more .. this can be done here or in a customized
+	// check_attack code for the hover.
+	if (self->flags & FL_FLY)
+	{
+		// originally, just 0.3
+		float strafe_chance;
+		if (!(strcmp(self->classname, "monster_daedalus")))
+			strafe_chance = 0.8;
+		else
+			strafe_chance = 0.6;
+
+		// if enemy is tesla, never strafe
+		if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla")))
+			strafe_chance = 0;
+
+		if (qrandom() < strafe_chance)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+// do we want the monsters strafing?
+#ifdef SLIDING_TROOPS
+	else
+	{
+		if (qrandom() < 0.4)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+#endif
+//-PMM
+
+	return false;
+}
+
+
+/*
+=============
+ai_run_melee
+
+Turn and close until within an angle to launch a melee attack
+=============
+*/
+void ai_run_melee(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+		M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.melee (self);
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+}
+
+
+/*
+=============
+ai_run_missile
+
+Turn in place until within an angle to launch a missile attack
+=============
+*/
+void ai_run_missile(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+		M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.attack (self);
+//		if (self->monsterinfo.attack_state == AS_MISSILE)
+		if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND))
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+//		else if (self->monsterinfo.attack_state != AS_SLIDING)
+//			gi.dprintf ("ai_run_missile: Unexpected attack state %d !\n", self->monsterinfo.attack_state);
+	}
+};
+
+
+/*
+=============
+ai_run_slide
+
+Strafe sideways, but stay at aproximately the same range
+=============
+*/
+void ai_run_slide(edict_t *self, float distance)
+{
+	float	ofs;
+	float	angle;
+
+	self->ideal_yaw = enemy_yaw;
+
+//	if (self->flags & FL_FLY)
+//		angle = 90;
+//	else
+//		angle = 45;
+	
+	angle = 90;
+	
+	if (self->monsterinfo.lefty)
+		ofs = angle;
+	else
+		ofs = -angle;
+//	
+//	if (!(self->flags & FL_FLY))
+//	{
+//		// non fliers should actually turn towards the direction their trying to run
+//		self->ideal_yaw += ofs;
+//	}
+//
+	if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+		M_ChangeYaw (self);
+
+	/*
+	if (!(self->flags & FL_FLY))
+	{
+		if (M_walkmove (self, self->ideal_yaw + ofs, distance))
+			return;
+	}
+	else
+	{
+		if (M_walkmove (self, self->ideal_yaw, distance))
+			return;
+	}
+	*/
+	// PMM - clamp maximum sideways move for non flyers to make them look less jerky
+	if (!self->flags & FL_FLY)
+		distance = qmin(distance, MAX_SIDESTEP);
+	if (M_walkmove (self, self->ideal_yaw + ofs, distance))
+		return;
+	// PMM - if we're dodging, give up on it and go straight
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		monster_done_dodge (self);
+		// by setting as_straight, caller will know to try straight move
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+		return;
+	}
+
+	self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+	if (M_walkmove (self, self->ideal_yaw - ofs, distance))
+		return;
+	// PMM - if we're dodging, give up on it and go straight
+	if (self->monsterinfo.aiflags & AI_DODGING)
+		monster_done_dodge (self);
+
+	// PMM - the move failed, so signal the caller (ai_run) to try going straight
+	self->monsterinfo.attack_state = AS_STRAIGHT;
+	/*
+	if (!(self->flags & FL_FLY))
+	{
+		M_walkmove (self, self->ideal_yaw + ofs, distance);
+	}
+	else
+	{
+		M_walkmove (self, self->ideal_yaw, distance);
+	}*/
+}
+
+
+/*
+=============
+ai_checkattack
+
+Decides if we're going to attack or do something else
+used by ai_run and ai_stand
+=============
+*/
+qboolean ai_checkattack (edict_t *self, float)
+{
+	vec3_t		temp;
+	qboolean	hesDeadJim;
+	// PMM
+	qboolean	retval;
+
+// this causes monsters to run blindly to the combat point w/o firing
+	if (self->goalentity)
+	{
+		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+			return false;
+
+		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+		{
+			if ((level.time - self->enemy->teleport_time) > 5.0)
+			{
+				if (self->goalentity == self->enemy)
+					if (self->movetarget)
+						self->goalentity = self->movetarget;
+					else
+						self->goalentity = NULL;
+				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			}
+			else
+			{
+				self->show_hostile = level.time + 1;
+				return false;
+			}
+		}
+	}
+
+	enemy_vis = false;
+
+// see if the enemy is dead
+	hesDeadJim = false;
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		hesDeadJim = true;
+	}
+	else if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (!(self->enemy->inuse) || (self->enemy->health > 0))
+		{
+			hesDeadJim = true;
+//			self->monsterinfo.aiflags &= ~AI_MEDIC;
+		}
+	}
+	else
+	{
+		if (self->monsterinfo.aiflags & AI_BRUTAL)
+		{
+			if (self->enemy->health <= -80)
+				hesDeadJim = true;
+		}
+		else
+		{
+			if (self->enemy->health <= 0)
+				hesDeadJim = true;
+		}
+	}
+
+	if (hesDeadJim)
+	{
+		self->monsterinfo.aiflags &= ~AI_MEDIC;
+		self->enemy = NULL;
+	// FIXME: look all around for other targets
+		if (self->oldenemy && self->oldenemy->health > 0)
+		{
+			self->enemy = self->oldenemy;
+			self->oldenemy = NULL;
+			HuntTarget (self);
+		}
+//ROGUE - multiple teslas make monsters lose track of the player.
+		else if(self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf("resorting to last_player_enemy...\n");
+			self->enemy = self->monsterinfo.last_player_enemy;
+			self->oldenemy = NULL;
+			self->monsterinfo.last_player_enemy = NULL;
+			HuntTarget (self);
+		}
+//ROGUE
+		else
+		{
+			if (self->movetarget)
+			{
+				self->goalentity = self->movetarget;
+				self->monsterinfo.walk (self);
+			}
+			else
+			{
+				// we need the pausetime otherwise the stand code
+				// will just revert to walking with no target and
+				// the monsters will wonder around aimlessly trying
+				// to hunt the world entity
+				self->monsterinfo.pausetime = level.time + 100000000;
+				self->monsterinfo.stand (self);
+			}
+			return true;
+		}
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+// check knowledge of enemy
+	enemy_vis = visible(self, self->enemy);
+	if (enemy_vis)
+	{
+		self->monsterinfo.search_time = level.time + 5;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+		// PMM
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+		self->monsterinfo.trail_time = level.time;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+		self->monsterinfo.blind_fire_delay = 0;
+		// pmm
+	}
+
+// look for other coop players here
+//	if (coop && self->monsterinfo.search_time < level.time)
+//	{
+//		if (FindTarget (self))
+//			return true;
+//	}
+
+	enemy_infront = infront(self, self->enemy);
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+
+	// JDC self->ideal_yaw = enemy_yaw;
+
+	// PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis
+	// stuff .. this allows for, among other things, circle strafing and attacking while in ai_run
+	retval = self->monsterinfo.checkattack (self);
+	if (retval)
+	{
+		// PMM
+		if (self->monsterinfo.attack_state == AS_MISSILE)
+		{
+			ai_run_missile (self);
+			return true;
+		}
+		if (self->monsterinfo.attack_state == AS_MELEE)
+		{
+			ai_run_melee (self);
+			return true;
+		}
+		// PMM -- added so monsters can shoot blind
+		if (self->monsterinfo.attack_state == AS_BLIND)
+		{
+			ai_run_missile (self);
+			return true;
+		}
+		// pmm
+
+		// if enemy is not currently visible, we will never attack
+		if (!enemy_vis)
+			return false;
+		// PMM
+	}
+	return retval;
+	// PMM
+//	return self->monsterinfo.checkattack (self);
+}
+
+
+/*
+=============
+ai_run
+
+The monster has an enemy it is trying to kill
+=============
+*/
+void ai_run (edict_t *self, float dist)
+{
+	vec3_t		v;
+	edict_t		*tempgoal;
+	edict_t		*save;
+	qboolean	new;
+	edict_t		*marker;
+	float		d1, d2;
+	trace_t		tr;
+	vec3_t		v_forward, v_right;
+	float		left, center, right;
+	vec3_t		left_target, right_target;
+	//PMM
+	qboolean	retval;
+	qboolean	alreadyMoved = false;
+	qboolean	gotcha = false;
+	edict_t		*realEnemy;
+ 
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+	{
+		M_MoveToGoal (self, dist);
+		return;
+	}
+
+	// PMM
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%s - duck flag cleaned up!\n", self->classname);
+		self->monsterinfo.aiflags &= ~AI_DUCKED;
+	}
+	if (self->maxs[2] != self->monsterinfo.base_height)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%s - ducked height corrected!\n", self->classname);
+		monster_duck_up (self);
+	}
+//	if ((self->monsterinfo.aiflags & AI_MANUAL_STEERING) && (strcmp(self->classname, "monster_turret")))
+//	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%s - manual steering in ai_run!\n", self->classname);
+//	}
+	// pmm
+
+//==========
+//PGM
+	// if we're currently looking for a hint path
+	if (self->monsterinfo.aiflags & AI_HINT_PATH)
+	{
+		// determine direction to our destination hintpath.
+		// FIXME - is this needed EVERY time? I was having trouble with them
+		// sometimes not going after it, and this fixed it.
+//		VectorSubtract(self->movetarget->s.origin, self->s.origin, v);
+//		vectoangles(v, v_forward);
+//		self->ideal_yaw = v_forward[YAW];
+//		gi.dprintf("seeking hintpath. origin: %s %0.1f\n", vtos(v), self->ideal_yaw);
+		M_MoveToGoal (self, dist);
+		if(!self->inuse)
+			return;			// PGM - g_touchtrigger free problem
+//		return;
+
+		// if we've already seen the player, and can't see him now, return
+//		if(self->enemy && !visible(self, self->enemy))
+//			return;
+
+		// if not and can't find the player, return
+//		if(!FindTarget(self))
+//			return;
+
+		// first off, make sure we're looking for the player, not a noise he made
+		if (self->enemy)
+		{
+			if (self->enemy->inuse)
+			{
+				if (strcmp(self->enemy->classname, "player_noise") != 0)
+					realEnemy = self->enemy;
+				else if (self->enemy->owner)
+					realEnemy = self->enemy->owner;
+				else // uh oh, can't figure out enemy, bail
+				{
+					self->enemy = NULL;
+					hintpath_stop (self);
+					return;
+				}
+			}
+			else
+			{
+				self->enemy = NULL;
+				hintpath_stop (self);
+				return;
+			}
+		}
+		else
+		{
+			hintpath_stop (self);
+			return;
+		}
+
+		if (coop && coop->value)
+		{
+			// if we're in coop, check my real enemy first .. if I SEE him, set gotcha to true
+			if (self->enemy && visible(self, realEnemy))
+				gotcha = true;
+			else // otherwise, let FindTarget bump us out of hint paths, if appropriate
+				FindTarget(self);
+		}
+		else
+		{
+			if(self->enemy && visible(self, realEnemy))
+				gotcha = true;
+		}
+		
+		// if we see the player, stop following hintpaths.
+		if (gotcha)
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("stopped following hint paths in ai_run\n");
+
+			// disconnect from hintpaths and start looking normally for players.
+			hintpath_stop (self);
+			// pmm - no longer needed, since hintpath_stop does it
+//			HuntTarget(self);
+		}
+		return;
+	}
+//PGM
+//==========
+
+	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+	{
+		// PMM - paranoia checking
+		if (self->enemy)
+			VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+
+		if ((!self->enemy) || (VectorLength(v) < 64))
+		// pmm
+		{
+			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			self->monsterinfo.stand (self);
+			return;
+		}
+
+		M_MoveToGoal (self, dist);
+		// PMM - prevent double moves for sound_targets
+		alreadyMoved = true;
+		// pmm
+		if(!self->inuse)
+			return;			// PGM - g_touchtrigger free problem
+
+		if (!FindTarget (self))
+			return;
+	}
+
+	// PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging
+
+	// PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING
+
+	retval = ai_checkattack (self, dist);
+
+	// PMM - don't strafe if we can't see our enemy
+	if ((!enemy_vis) && (self->monsterinfo.attack_state == AS_SLIDING))
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	// unless we're dodging (dodging out of view looks smart)
+	if (self->monsterinfo.aiflags & AI_DODGING)
+		self->monsterinfo.attack_state = AS_SLIDING;
+	// pmm
+
+	if (self->monsterinfo.attack_state == AS_SLIDING)
+	{
+		// PMM - protect against double moves
+		if (!alreadyMoved)
+			ai_run_slide (self, dist);
+		// PMM
+		// we're using attack_state as the return value out of ai_run_slide to indicate whether or not the
+		// move succeeded.  If the move succeeded, and we're still sliding, we're done in here (since we've
+		// had our chance to shoot in ai_checkattack, and have moved).
+		// if the move failed, our state is as_straight, and it will be taken care of below
+		if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING))
+			return;
+	}
+	else if (self->monsterinfo.aiflags & AI_CHARGING)
+	{
+		self->ideal_yaw = enemy_yaw;
+		if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
+			M_ChangeYaw (self);
+	}
+	if (retval)
+	{
+		// PMM - is this useful?  Monsters attacking usually call the ai_charge routine..
+		// the only monster this affects should be the soldier
+		if ((dist != 0) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && (!(self->monsterinfo.aiflags & AI_STAND_GROUND)))
+		{
+			M_MoveToGoal (self, dist);
+		}
+		if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
+		{
+			self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+			VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+			self->monsterinfo.trail_time = level.time;
+			//PMM
+			VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+			self->monsterinfo.blind_fire_delay = 0;
+			//pmm
+		}
+		return;
+	}
+	//PMM
+//	if (ai_checkattack (self, dist))
+//		return;
+
+//	if (self->monsterinfo.attack_state == AS_SLIDING)
+//	{
+//		ai_run_slide (self, dist);
+//		return;
+//	}
+
+	// PGM - added a little paranoia checking here... 9/22/98
+	if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
+	{
+//		if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+//			gi.dprintf("regained sight\n");
+		// PMM - check for alreadyMoved
+		if (!alreadyMoved)
+			M_MoveToGoal (self, dist);
+		if(!self->inuse)
+			return;			// PGM - g_touchtrigger free problem
+
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+		self->monsterinfo.trail_time = level.time;
+		// PMM
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
+		self->monsterinfo.blind_fire_delay = 0;
+		// pmm
+		return;
+	}
+
+//=======
+//PGM
+	// if we've been looking (unsuccessfully) for the player for 10 seconds
+	// PMM - reduced to 5, makes them much nastier
+	if((self->monsterinfo.trail_time + 5) <= level.time)
+	{
+		// and we haven't checked for valid hint paths in the last 10 seconds
+		if((self->monsterinfo.last_hint_time + 10) <= level.time)
+		{
+			// check for hint_paths.
+			self->monsterinfo.last_hint_time = level.time;
+			if(monsterlost_checkhint(self))
+				return;
+		}
+	}
+//PGM
+//=======
+
+// PMM - moved down here to allow monsters to get on hint paths
+	// coop will change to another enemy if visible
+	if (coop->value)
+	{	// FIXME: insane guys get mad with this, which causes crashes!
+		if (FindTarget (self))
+			return;
+	}
+// pmm
+
+	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
+	{
+		// PMM - double move protection
+		if (!alreadyMoved)
+			M_MoveToGoal (self, dist);
+		self->monsterinfo.search_time = 0;
+//		gi.dprintf("search timeout\n");
+		return;
+	}
+
+	save = self->goalentity;
+	tempgoal = G_Spawn();
+	self->goalentity = tempgoal;
+
+	new = false;
+
+	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
+	{
+		// just lost sight of the player, decide where to go first
+//		gi.dprintf("lost sight of player, last seen at %s\n", vtos(self->monsterinfo.last_sighting));
+		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
+		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+		new = true;
+	}
+
+	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
+	{
+//		vec3_t	debug_vec;
+
+		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
+//		VectorSubtract(self->monsterinfo.last_sighting, self->s.origin, debug_vec); 
+//		gi.dprintf("reached current goal: %s %s %f", vtos(self->s.origin), vtos(self->monsterinfo.last_sighting), VectorLength(debug_vec));
+
+		// give ourself more time since we got this far
+		self->monsterinfo.search_time = level.time + 5;
+
+		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
+		{
+//			gi.dprintf("was temp goal; retrying original\n");
+			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
+			marker = NULL;
+			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
+			new = true;
+		}
+		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
+		{
+			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
+			marker = PlayerTrail_PickFirst (self);
+		}
+		else
+		{
+			marker = PlayerTrail_PickNext (self);
+		}
+
+		if (marker)
+		{
+			VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
+			self->monsterinfo.trail_time = marker->timestamp;
+			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
+//			gi.dprintf("heading is %0.1f\n", self->ideal_yaw);
+
+//			debug_drawline(self.origin, self.last_sighting, 52);
+			new = true;
+		}
+	}
+
+	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
+	d1 = VectorLength(v);
+	if (d1 <= dist)
+	{
+		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
+		dist = d1;
+	}
+
+	VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
+
+	if (new)
+	{
+//		gi.dprintf("checking for course correction\n");
+
+		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
+		if (tr.fraction < 1)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			d1 = VectorLength(v);
+			center = tr.fraction;
+			d2 = d1 * ((center+1)/2);
+			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+			AngleVectors(self->s.angles, v_forward, v_right, NULL);
+
+			VectorSet(v, d2, -16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
+			left = tr.fraction;
+
+			VectorSet(v, d2, 16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
+			right = tr.fraction;
+
+			center = (d1*center)/d2;
+			if (left >= center && left > right)
+			{
+				if (left < 1)
+				{
+					VectorSet(v, d2 * left * 0.5, -16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (left_target, self->goalentity->s.origin);
+				VectorCopy (left_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted left\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+			else if (right >= center && right > left)
+			{
+				if (right < 1)
+				{
+					VectorSet(v, d2 * right * 0.5, 16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (right_target, self->goalentity->s.origin);
+				VectorCopy (right_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted right\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+		}
+//		else gi.dprintf("course was fine\n");
+	}
+
+	M_MoveToGoal (self, dist);
+	if(!self->inuse)
+		return;			// PGM - g_touchtrigger free problem
+
+	G_FreeEdict(tempgoal);
+
+	if (self)
+		self->goalentity = save;
+}
--- /dev/null
+++ b/rogue/g_chase.c
@@ -1,0 +1,160 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void UpdateChaseCam(edict_t *ent)
+{
+	vec3_t o, ownerv, goal;
+	edict_t *targ;
+	vec3_t forward, right;
+	trace_t trace;
+	int i;
+	vec3_t oldgoal;
+	vec3_t angles;
+
+	// is our chase target gone?
+	if (!ent->client->chase_target->inuse
+		|| ent->client->chase_target->client->resp.spectator) {
+		edict_t *old = ent->client->chase_target;
+		ChaseNext(ent);
+		if (ent->client->chase_target == old) {
+			ent->client->chase_target = NULL;
+			ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+			return;
+		}
+	}
+
+	targ = ent->client->chase_target;
+
+	VectorCopy(targ->s.origin, ownerv);
+	VectorCopy(ent->s.origin, oldgoal);
+
+	ownerv[2] += targ->viewheight;
+
+	VectorCopy(targ->client->v_angle, angles);
+	if (angles[PITCH] > 56)
+		angles[PITCH] = 56;
+	AngleVectors (angles, forward, right, NULL);
+	VectorNormalize(forward);
+	VectorMA(ownerv, -30, forward, o);
+
+	if (o[2] < targ->s.origin[2] + 20)
+		o[2] = targ->s.origin[2] + 20;
+
+	// jump animation lifts
+	if (!targ->groundentity)
+		o[2] += 16;
+
+	trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+	VectorCopy(trace.endpos, goal);
+
+	VectorMA(goal, 2, forward, goal);
+
+	// pad for floors and ceilings
+	VectorCopy(goal, o);
+	o[2] += 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] -= 6;
+	}
+
+	VectorCopy(goal, o);
+	o[2] -= 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] += 6;
+	}
+
+	if (targ->deadflag)
+		ent->client->ps.pmove.pm_type = PM_DEAD;
+	else
+		ent->client->ps.pmove.pm_type = PM_FREEZE;
+
+	VectorCopy(goal, ent->s.origin);
+	for (i=0 ; i<3 ; i++)
+		ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
+
+	if (targ->deadflag) {
+		ent->client->ps.viewangles[ROLL] = 40;
+		ent->client->ps.viewangles[PITCH] = -15;
+		ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
+	} else {
+		VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
+		VectorCopy(targ->client->v_angle, ent->client->v_angle);
+	}
+
+	ent->viewheight = 0;
+	ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+	gi.linkentity(ent);
+}
+
+void ChaseNext(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i++;
+		if (i > maxclients->value)
+			i = 1;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (!e->client->resp.spectator)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
+
+void ChasePrev(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i--;
+		if (i < 1)
+			i = maxclients->value;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (!e->client->resp.spectator)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
+
+void GetChaseTarget(edict_t *ent)
+{
+	int i;
+	edict_t *other;
+
+	for (i = 1; i <= maxclients->value; i++) {
+		other = g_edicts + i;
+		if (other->inuse && !other->client->resp.spectator) {
+			ent->client->chase_target = other;
+			ent->client->update_chase = true;
+			UpdateChaseCam(ent);
+			return;
+		}
+	}
+	gi.centerprintf(ent, "No other players to chase.");
+}
+
--- /dev/null
+++ b/rogue/g_cmds.c
@@ -1,0 +1,1040 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+char *ClientTeam (edict_t *ent)
+{
+	char		*p;
+	static char	value[512];
+
+	value[0] = 0;
+
+	if (!ent->client)
+		return value;
+
+	strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
+	p = strchr(value, '/');
+	if (!p)
+		return value;
+
+	if ((int)(dmflags->value) & DF_MODELTEAMS)
+	{
+		*p = 0;
+		return value;
+	}
+
+	// if ((int)(dmflags->value) & DF_SKINTEAMS)
+	return ++p;
+}
+
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2)
+{
+	char	ent1Team [512];
+	char	ent2Team [512];
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		return false;
+
+	strcpy (ent1Team, ClientTeam (ent1));
+	strcpy (ent2Team, ClientTeam (ent2));
+
+	if (strcmp(ent1Team, ent2Team) == 0)
+		return true;
+	return false;
+}
+
+
+void SelectNextItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (cl->chase_target) {
+		ChaseNext(ent);
+		return;
+	}
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void SelectPrevItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (cl->chase_target) {
+		ChasePrev(ent);
+		return;
+	}
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void ValidateSelectedItem (edict_t *ent)
+{
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	if (cl->pers.inventory[cl->pers.selected_item])
+		return;		// valid
+
+	SelectNextItem (ent, -1);
+}
+
+
+//=================================================================================
+
+/*
+==================
+Cmd_Give_f
+
+Give items to a client
+==================
+*/
+void Cmd_Give_f (edict_t *ent)
+{
+	char		*name;
+	gitem_t		*it;
+	int			index;
+	int			i;
+	qboolean	give_all;
+	edict_t		*it_ent;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	name = gi.args();
+
+	if (cistrcmp(name, "all") == 0)
+		give_all = true;
+	else
+		give_all = false;
+
+	if (give_all || cistrcmp(gi.argv(1), "health") == 0)
+	{
+		if (gi.argc() == 3)
+			ent->health = atoi(gi.argv(2));
+		else
+			ent->health = ent->max_health;
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "weapons") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_WEAPON))
+				continue;
+			ent->client->pers.inventory[i] += 1;
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "ammo") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_AMMO))
+				continue;
+			Add_Ammo (ent, it, 1000);
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "armor") == 0)
+	{
+		gitem_armor_t	*info;
+
+		it = FindItem("Jacket Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Combat Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Body Armor");
+		info = (gitem_armor_t *)it->info;
+		ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "Power Shield") == 0)
+	{
+		it = FindItem("Power Shield");
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (it->flags & IT_NOT_GIVEABLE)					// ROGUE
+				continue;										// ROGUE
+			if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO))
+				continue;
+			ent->client->pers.inventory[i] = 1;
+		}
+		return;
+	}
+
+	it = FindItem (name);
+	if (!it)
+	{
+		name = gi.argv(1);
+		it = FindItem (name);
+		if (!it)
+		{
+			gi.cprintf (ent, PRINT_HIGH, "unknown item\n");
+			return;
+		}
+	}
+
+	if (!it->pickup)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n");
+		return;
+	}
+
+//ROGUE
+	if (it->flags & IT_NOT_GIVEABLE)		
+	{
+		gi.dprintf ("item cannot be given\n");
+		return;							
+	}
+//ROGUE
+
+	index = ITEM_INDEX(it);
+
+	if (it->flags & IT_AMMO)
+	{
+		if (gi.argc() == 3)
+			ent->client->pers.inventory[index] = atoi(gi.argv(2));
+		else
+			ent->client->pers.inventory[index] += it->quantity;
+	}
+	else
+	{
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		// PMM - since some items don't actually spawn when you say to ..
+		if (!it_ent->inuse)
+			return;
+		// pmm
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+	}
+}
+
+
+/*
+==================
+Cmd_God_f
+
+Sets client to godmode
+
+argv(0) god
+==================
+*/
+void Cmd_God_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_GODMODE;
+	if (!(ent->flags & FL_GODMODE) )
+		msg = "godmode OFF\n";
+	else
+		msg = "godmode ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Notarget_f
+
+Sets client to notarget
+
+argv(0) notarget
+==================
+*/
+void Cmd_Notarget_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_NOTARGET;
+	if (!(ent->flags & FL_NOTARGET) )
+		msg = "notarget OFF\n";
+	else
+		msg = "notarget ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Noclip_f
+
+argv(0) noclip
+==================
+*/
+void Cmd_Noclip_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+	{
+		ent->movetype = MOVETYPE_WALK;
+		msg = "noclip OFF\n";
+	}
+	else
+	{
+		ent->movetype = MOVETYPE_NOCLIP;
+		msg = "noclip ON\n";
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Use_f
+
+Use an inventory item
+==================
+*/
+void Cmd_Use_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+		return;
+	}
+
+	it->use (ent, it);
+}
+
+
+/*
+==================
+Cmd_Drop_f
+
+Drop an inventory item
+==================
+*/
+void Cmd_Drop_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+		return;
+	}
+
+	it->drop (ent, it);
+}
+
+
+/*
+=================
+Cmd_Inven_f
+=================
+*/
+void Cmd_Inven_f (edict_t *ent)
+{
+	int			i;
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	cl->showscores = false;
+	cl->showhelp = false;
+
+	if (cl->showinventory)
+	{
+		cl->showinventory = false;
+		return;
+	}
+
+	cl->showinventory = true;
+
+	gi.WriteByte (svc_inventory);
+	for (i=0 ; i<MAX_ITEMS ; i++)
+	{
+		gi.WriteShort (cl->pers.inventory[i]);
+	}
+	gi.unicast (ent, true);
+}
+
+/*
+=================
+Cmd_InvUse_f
+=================
+*/
+void Cmd_InvUse_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to use.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+	it->use (ent, it);
+}
+
+/*
+=================
+Cmd_WeapPrev_f
+=================
+*/
+void Cmd_WeapPrev_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		// PMM - prevent scrolling through ALL weapons
+//		index = (selected_weapon + i)%MAX_ITEMS;
+		index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		// PMM - prevent scrolling through ALL weapons
+//		if (cl->pers.weapon == it)
+//			return;	// successful
+		if (cl->newweapon == it)
+			return;
+	}
+}
+
+/*
+=================
+Cmd_WeapNext_f
+=================
+*/
+void Cmd_WeapNext_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		// PMM - prevent scrolling through ALL weapons
+//		index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
+		index = (selected_weapon + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		// PMM - prevent scrolling through ALL weapons
+//		if (cl->pers.weapon == it)
+//			return;	// successful
+		if (cl->newweapon == it)
+			return;
+	}
+}
+
+/*
+=================
+Cmd_WeapLast_f
+=================
+*/
+void Cmd_WeapLast_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon || !cl->pers.lastweapon)
+		return;
+
+	index = ITEM_INDEX(cl->pers.lastweapon);
+	if (!cl->pers.inventory[index])
+		return;
+	it = &itemlist[index];
+	if (!it->use)
+		return;
+	if (! (it->flags & IT_WEAPON) )
+		return;
+	it->use (ent, it);
+}
+
+/*
+=================
+Cmd_InvDrop_f
+=================
+*/
+void Cmd_InvDrop_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+	it->drop (ent, it);
+}
+
+/*
+=================
+Cmd_Kill_f
+=================
+*/
+void Cmd_Kill_f (edict_t *ent)
+{
+	if((level.time - ent->client->respawn_time) < 5)
+		return;
+	ent->flags &= ~FL_GODMODE;
+	ent->health = 0;
+	meansOfDeath = MOD_SUICIDE;
+
+//ROGUE
+	// make sure no trackers are still hurting us.
+	if(ent->client->tracker_pain_framenum)
+		RemoveAttackingPainDaemons (ent);
+
+	if (ent->client->owned_sphere)
+	{
+		G_FreeEdict(ent->client->owned_sphere);
+		ent->client->owned_sphere = NULL;
+	}
+//ROGUE
+
+	player_die (ent, ent, ent, 100000, vec3_origin);
+}
+
+/*
+=================
+Cmd_PutAway_f
+=================
+*/
+void Cmd_PutAway_f (edict_t *ent)
+{
+	ent->client->showscores = false;
+	ent->client->showhelp = false;
+	ent->client->showinventory = false;
+}
+
+
+int PlayerSort (void const *a, void const *b)
+{
+	int		anum, bnum;
+
+	anum = *(int *)a;
+	bnum = *(int *)b;
+
+	anum = game.clients[anum].ps.stats[STAT_FRAGS];
+	bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
+
+	if (anum < bnum)
+		return -1;
+	if (anum > bnum)
+		return 1;
+	return 0;
+}
+
+/*
+=================
+Cmd_Players_f
+=================
+*/
+void Cmd_Players_f (edict_t *ent)
+{
+	int		i;
+	int		count;
+	char	small[64];
+	char	large[1280];
+	int		index[256];
+
+	count = 0;
+	for (i = 0 ; i < maxclients->value ; i++)
+		if (game.clients[i].pers.connected)
+		{
+			index[count] = i;
+			count++;
+		}
+
+	// sort by frags
+	qsort (index, count, sizeof(index[0]), PlayerSort);
+
+	// print information
+	large[0] = 0;
+
+	for (i = 0 ; i < count ; i++)
+	{
+		Com_sprintf (small, sizeof(small), "%3i %s\n",
+			game.clients[index[i]].ps.stats[STAT_FRAGS],
+			game.clients[index[i]].pers.netname);
+		if (strlen (small) + strlen(large) > sizeof(large) - 100 )
+		{	// can't print all of them in one packet
+			strcat (large, "...\n");
+			break;
+		}
+		strcat (large, small);
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count);
+}
+
+/*
+=================
+Cmd_Wave_f
+=================
+*/
+void Cmd_Wave_f (edict_t *ent)
+{
+	int		i;
+
+	i = atoi (gi.argv(1));
+
+	// can't wave when ducked
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+		return;
+
+	if (ent->client->anim_priority > ANIM_WAVE)
+		return;
+
+	ent->client->anim_priority = ANIM_WAVE;
+
+	switch (i)
+	{
+	case 0:
+		gi.cprintf (ent, PRINT_HIGH, "flipoff\n");
+		ent->s.frame = FRAME_flip01-1;
+		ent->client->anim_end = FRAME_flip12;
+		break;
+	case 1:
+		gi.cprintf (ent, PRINT_HIGH, "salute\n");
+		ent->s.frame = FRAME_salute01-1;
+		ent->client->anim_end = FRAME_salute11;
+		break;
+	case 2:
+		gi.cprintf (ent, PRINT_HIGH, "taunt\n");
+		ent->s.frame = FRAME_taunt01-1;
+		ent->client->anim_end = FRAME_taunt17;
+		break;
+	case 3:
+		gi.cprintf (ent, PRINT_HIGH, "wave\n");
+		ent->s.frame = FRAME_wave01-1;
+		ent->client->anim_end = FRAME_wave11;
+		break;
+	case 4:
+	default:
+		gi.cprintf (ent, PRINT_HIGH, "point\n");
+		ent->s.frame = FRAME_point01-1;
+		ent->client->anim_end = FRAME_point12;
+		break;
+	}
+}
+
+/*
+==================
+Cmd_Say_f
+==================
+*/
+void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0)
+{
+	int		i, j;
+	edict_t	*other;
+	char	*p;
+	char	text[2048];
+	gclient_t *cl;
+
+	if (gi.argc () < 2 && !arg0)
+		return;
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		team = false;
+
+	if (team)
+		Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname);
+	else
+		Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
+
+	if (arg0)
+	{
+		strcat (text, gi.argv(0));
+		strcat (text, " ");
+		strcat (text, gi.args());
+	}
+	else
+	{
+		p = gi.args();
+
+		if (*p == '"')
+		{
+			p++;
+			p[strlen(p)-1] = 0;
+		}
+		strcat(text, p);
+	}
+
+	// don't let text be too long for malicious reasons
+	if (strlen(text) > 150)
+		text[150] = 0;
+
+	strcat(text, "\n");
+
+	if (flood_msgs->value) {
+		cl = ent->client;
+
+        if (level.time < cl->flood_locktill) {
+			gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
+				(int)(cl->flood_locktill - level.time));
+            return;
+        }
+        i = cl->flood_whenhead - flood_msgs->value + 1;
+        if (i < 0)
+            i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i;
+		if (cl->flood_when[i] && 
+			level.time - cl->flood_when[i] < flood_persecond->value) {
+			cl->flood_locktill = level.time + flood_waitdelay->value;
+			gi.cprintf(ent, PRINT_CHAT, "Flood protection:  You can't talk for %d seconds.\n",
+				(int)flood_waitdelay->value);
+            return;
+        }
+		cl->flood_whenhead = (cl->flood_whenhead + 1) %
+			(sizeof(cl->flood_when)/sizeof(cl->flood_when[0]));
+		cl->flood_when[cl->flood_whenhead] = level.time;
+	}
+
+	if (dedicated->value)
+		gi.cprintf(NULL, PRINT_CHAT, "%s", text);
+
+	for (j = 1; j <= game.maxclients; j++)
+	{
+		other = &g_edicts[j];
+		if (!other->inuse)
+			continue;
+		if (!other->client)
+			continue;
+		if (team)
+		{
+			if (!OnSameTeam(ent, other))
+				continue;
+		}
+		gi.cprintf(other, PRINT_CHAT, "%s", text);
+	}
+}
+
+//======
+//ROGUE
+void Cmd_Ent_Count_f (edict_t *)
+{
+	int		x;
+	edict_t	*e;
+
+	x=0;
+
+	for (e=g_edicts;e < &g_edicts[globals.num_edicts] ; e++)
+	{
+		if(e->inuse)
+			x++;
+	}
+
+	gi.dprintf("%d entites active\n", x);
+}
+//ROGUE
+//======
+
+void Cmd_PlayerList_f(edict_t *ent)
+{
+	int i;
+	char st[80];
+	char text[1400];
+	edict_t *e2;
+
+	// connect time, ping, score, name
+	*text = 0;
+	for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) {
+		if (!e2->inuse)
+			continue;
+
+		Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n",
+			(level.framenum - e2->client->resp.enterframe) / 600,
+			((level.framenum - e2->client->resp.enterframe) % 600)/10,
+			e2->client->ping,
+			e2->client->resp.score,
+			e2->client->pers.netname,
+			e2->client->resp.spectator ? " (spectator)" : "");
+		if (strlen(text) + strlen(st) > sizeof(text) - 50) {
+			sprintf(text+strlen(text), "And more...\n");
+			gi.cprintf(ent, PRINT_HIGH, "%s", text);
+			return;
+		}
+		strcat(text, st);
+	}
+	gi.cprintf(ent, PRINT_HIGH, "%s", text);
+}
+
+
+/*
+=================
+ClientCommand
+=================
+*/
+void ClientCommand (edict_t *ent)
+{
+	char	*cmd;
+
+	if (!ent->client)
+		return;		// not fully in game yet
+
+	cmd = gi.argv(0);
+
+	if (cistrcmp (cmd, "players") == 0)
+	{
+		Cmd_Players_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "say") == 0)
+	{
+		Cmd_Say_f (ent, false, false);
+		return;
+	}
+	if (cistrcmp (cmd, "say_team") == 0)
+	{
+		Cmd_Say_f (ent, true, false);
+		return;
+	}
+	if (cistrcmp (cmd, "score") == 0)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "help") == 0)
+	{
+		Cmd_Help_f (ent);
+		return;
+	}
+
+	if (level.intermissiontime)
+		return;
+
+	if (cistrcmp (cmd, "use") == 0)
+		Cmd_Use_f (ent);
+	else if (cistrcmp (cmd, "drop") == 0)
+		Cmd_Drop_f (ent);
+	else if (cistrcmp (cmd, "give") == 0)
+		Cmd_Give_f (ent);
+	else if (cistrcmp (cmd, "god") == 0)
+		Cmd_God_f (ent);
+	else if (cistrcmp (cmd, "notarget") == 0)
+		Cmd_Notarget_f (ent);
+	else if (cistrcmp (cmd, "noclip") == 0)
+		Cmd_Noclip_f (ent);
+	else if (cistrcmp (cmd, "inven") == 0)
+		Cmd_Inven_f (ent);
+	else if (cistrcmp (cmd, "invnext") == 0)
+		SelectNextItem (ent, -1);
+	else if (cistrcmp (cmd, "invprev") == 0)
+		SelectPrevItem (ent, -1);
+	else if (cistrcmp (cmd, "invnextw") == 0)
+		SelectNextItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invprevw") == 0)
+		SelectPrevItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invnextp") == 0)
+		SelectNextItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invprevp") == 0)
+		SelectPrevItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invuse") == 0)
+		Cmd_InvUse_f (ent);
+	else if (cistrcmp (cmd, "invdrop") == 0)
+		Cmd_InvDrop_f (ent);
+	else if (cistrcmp (cmd, "weapprev") == 0)
+		Cmd_WeapPrev_f (ent);
+	else if (cistrcmp (cmd, "weapnext") == 0)
+		Cmd_WeapNext_f (ent);
+	else if (cistrcmp (cmd, "weaplast") == 0)
+		Cmd_WeapLast_f (ent);
+	else if (cistrcmp (cmd, "kill") == 0)
+		Cmd_Kill_f (ent);
+	else if (cistrcmp (cmd, "putaway") == 0)
+		Cmd_PutAway_f (ent);
+	else if (cistrcmp (cmd, "wave") == 0)
+		Cmd_Wave_f (ent);
+	else if (cistrcmp(cmd, "playerlist") == 0)
+		Cmd_PlayerList_f(ent);
+	else if (cistrcmp (cmd, "entcount") == 0)		// PGM
+		Cmd_Ent_Count_f (ent);						// PGM
+	else if (cistrcmp (cmd, "disguise") == 0)		// PGM
+	{
+		ent->flags |= FL_DISGUISED;
+	}
+	else	// anything that doesn't match a command will be a chat
+		Cmd_Say_f (ent, false, true);
+}
--- /dev/null
+++ b/rogue/g_combat.c
@@ -1,0 +1,948 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void M_SetEffects (edict_t *self);
+
+/*
+ROGUE
+clean up heal targets for medic
+*/
+void cleanupHealTarget (edict_t *ent)
+{
+	ent->monsterinfo.healer = NULL;
+	ent->takedamage = DAMAGE_YES;
+	ent->monsterinfo.aiflags &= ~AI_RESURRECTING;
+	M_SetEffects (ent);
+}
+/*
+============
+CanDamage
+
+Returns true if the inflictor can directly damage the target.  Used for
+explosions and melee attacks.
+============
+*/
+qboolean CanDamage (edict_t *targ, edict_t *inflictor)
+{
+	vec3_t	dest;
+	trace_t	trace;
+
+// bmodels need special checking because their origin is 0,0,0
+	if (targ->movetype == MOVETYPE_PUSH)
+	{
+		VectorAdd (targ->absmin, targ->absmax, dest);
+		VectorScale (dest, 0.5, dest);
+		trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+		if (trace.fraction == 1.0)
+			return true;
+		if (trace.ent == targ)
+			return true;
+		return false;
+	}
+	
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+
+	return false;
+}
+
+
+/*
+============
+Killed
+============
+*/
+void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+	if (targ->health < -999)
+		targ->health = -999;
+
+	if (targ->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (targ->enemy)  // god, I hope so
+		{
+			cleanupHealTarget (targ->enemy);
+		}
+
+		// clean up self
+		targ->monsterinfo.aiflags &= ~AI_MEDIC;
+		targ->enemy = attacker;
+	}
+	else
+	{
+		targ->enemy = attacker;
+	}
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+//		targ->svflags |= SVF_DEADMONSTER;	// now treat as a different content type
+		//ROGUE - free up slot for spawned monster if it's spawned
+		if (targ->monsterinfo.aiflags & AI_SPAWNED_CARRIER)
+		{
+			if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse && 
+				!strcmp(targ->monsterinfo.commander->classname, "monster_carrier"))
+			{
+				targ->monsterinfo.commander->monsterinfo.monster_slots++;
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("g_combat: freeing up carrier slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots);
+			}
+		}
+		if (targ->monsterinfo.aiflags & AI_SPAWNED_MEDIC_C)
+		{
+			if (targ->monsterinfo.commander)
+			{
+				if (targ->monsterinfo.commander->inuse && !strcmp(targ->monsterinfo.commander->classname, "monster_medic_commander"))
+				{
+					targ->monsterinfo.commander->monsterinfo.monster_slots++;
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("g_combat: freeing up medic slot - %d left\n", targ->monsterinfo.commander->monsterinfo.monster_slots);
+				}
+//				else
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("my commander is dead!  he's a %s\n", targ->monsterinfo.commander->classname);
+			}
+//			else if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("My commander is GONE\n");
+
+		}
+		if (targ->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
+		{
+			// need to check this because we can have variable numbers of coop players
+			if (targ->monsterinfo.commander && targ->monsterinfo.commander->inuse && 
+				!strncmp(targ->monsterinfo.commander->classname, "monster_widow", 13))
+			{
+				if (targ->monsterinfo.commander->monsterinfo.monster_used > 0)
+					targ->monsterinfo.commander->monsterinfo.monster_used--;
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("g_combat: freeing up black widow slot - %d used\n", targ->monsterinfo.commander->monsterinfo.monster_used);
+			}
+		}
+		//rogue
+		if ((!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(targ->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+		{
+			level.killed_monsters++;
+			if (coop->value && attacker->client)
+				attacker->client->resp.score++;
+			// medics won't heal monsters that they kill themselves
+			// PMM - now they will
+//			if (strcmp(attacker->classname, "monster_medic") == 0)
+//				targ->owner = attacker;
+		}
+	}
+
+	if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
+	{	// doors, triggers, etc
+		targ->die (targ, inflictor, attacker, damage, point);
+		return;
+	}
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+		targ->touch = NULL;
+		monster_death_use (targ);
+	}
+
+	targ->die (targ, inflictor, attacker, damage, point);
+}
+
+
+/*
+================
+SpawnDamage
+================
+*/
+void SpawnDamage (int type, vec3_t origin, vec3_t normal, int /*damage*/)
+{
+	//if (damage > 255)
+	//	damage = 255;
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (type);
+//	gi.WriteByte (damage);
+	gi.WritePosition (origin);
+	gi.WriteDir (normal);
+	gi.multicast (origin, MULTICAST_PVS);
+}
+
+
+/*
+============
+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
+point		point at which the damage is being inflicted
+normal		normal vector from that point
+damage		amount of damage being inflicted
+knockback	force to be applied against targ as a result of the damage
+
+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_ENERGY			damage is from an energy based weapon
+	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
+	DAMAGE_BULLET			damage is from a bullet (used for ricochets)
+	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
+============
+*/
+static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			power_armor_type;
+	int			index = 0;
+	int			damagePerCell;
+	int			pa_te_type;
+	int			power = 0;
+	int			power_used;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR))		// PGM
+		return 0;					
+
+	if (client)
+	{
+		power_armor_type = PowerArmorType (ent);
+		if (power_armor_type != POWER_ARMOR_NONE)
+		{
+			index = ITEM_INDEX(FindItem("Cells"));
+			power = client->pers.inventory[index];
+		}
+	}
+	else if (ent->svflags & SVF_MONSTER)
+	{
+		power_armor_type = ent->monsterinfo.power_armor_type;
+		power = ent->monsterinfo.power_armor_power;
+	}
+	else
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_NONE)
+		return 0;
+	if (!power)
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_SCREEN)
+	{
+		vec3_t		vec;
+		float		dot;
+		vec3_t		forward;
+
+		// only works if damage point is in front
+		AngleVectors (ent->s.angles, forward, NULL, NULL);
+		VectorSubtract (point, ent->s.origin, vec);
+		VectorNormalize (vec);
+		dot = DotProduct (vec, forward);
+		if (dot <= 0.3)
+			return 0;
+
+		damagePerCell = 1;
+		pa_te_type = TE_SCREEN_SPARKS;
+		damage = damage / 3;
+	}
+	else
+	{
+		damagePerCell = 2;
+		pa_te_type = TE_SHIELD_SPARKS;
+		damage = (2 * damage) / 3;
+	}
+
+	// etf rifle
+	if (dflags & DAMAGE_NO_REG_ARMOR)
+		save = (power * damagePerCell) / 2;
+	else
+		save = power * damagePerCell;
+
+	if (!save)
+		return 0;
+	if (save > damage)
+		save = damage;
+
+	SpawnDamage (pa_te_type, point, normal, save);
+	ent->powerarmor_time = level.time + 0.2;
+
+	if (dflags & DAMAGE_NO_REG_ARMOR)
+		power_used = (save / damagePerCell) * 2;
+	else
+		power_used = save / damagePerCell;
+
+	if (client)
+		client->pers.inventory[index] -= power_used;
+	else
+		ent->monsterinfo.power_armor_power -= power_used;
+	return save;
+}
+
+static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			index;
+	gitem_t		*armor;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (!client)
+		return 0;
+
+	// ROGUE - added DAMAGE_NO_REG_ARMOR for atf rifle
+	if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR))
+		return 0;
+
+	index = ArmorIndex (ent);
+	if (!index)
+		return 0;
+
+	armor = GetItemByIndex (index);
+
+	if (dflags & DAMAGE_ENERGY)
+		save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
+	else
+		save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
+	if (save >= client->pers.inventory[index])
+		save = client->pers.inventory[index];
+
+	if (!save)
+		return 0;
+
+	client->pers.inventory[index] -= save;
+	SpawnDamage (te_sparks, point, normal, save);
+
+	return save;
+}
+
+void M_ReactToDamage (edict_t *targ, edict_t *attacker, edict_t *inflictor)
+{
+	// pmm
+	qboolean new_tesla;
+
+	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
+		return;
+
+//=======
+//ROGUE
+	// logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker)
+	// attack the tesla
+	// also, target the tesla if it's a "new" tesla
+	if ((inflictor) && (!strcmp(inflictor->classname, "tesla")))
+	{
+		new_tesla = MarkTeslaArea(targ, inflictor);
+		if (new_tesla)
+			TargetTesla (targ, inflictor);
+		return;
+		// FIXME - just ignore teslas when you're TARGET_ANGER or MEDIC
+/*		if (!(targ->enemy && (targ->monsterinfo.aiflags & (AI_TARGET_ANGER|AI_MEDIC))))
+		{
+			// FIXME - coop issues?
+			if ((!targ->enemy) || (!visible(targ, targ->enemy)))
+			{
+				gi.dprintf ("can't see player, switching to tesla\n");
+				TargetTesla (targ, inflictor);
+				return;
+			}
+			gi.dprintf ("can see player, ignoring tesla\n");
+		}
+		else if ((g_showlogic) && (g_showlogic->value))
+			gi.dprintf ("no enemy, or I'm doing other, more important things, than worrying about a damned tesla!\n");
+*/
+	}
+//ROGUE
+//=======
+
+	if (attacker == targ || attacker == targ->enemy)
+		return;
+
+	// if we are a good guy monster and our attacker is a player
+	// or another good guy, do not get mad at them
+	if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
+			return;
+	}
+
+//PGM
+	// if we're currently mad at something a target_anger made us mad at, ignore
+	// damage
+	if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
+	{
+		float	percentHealth;
+
+		// make sure whatever we were pissed at is still around.
+		if(targ->enemy->inuse)
+		{
+			percentHealth = (float)(targ->health) / (float)(targ->max_health);
+			if( targ->enemy->inuse && percentHealth > 0.33)
+				return;
+		}
+
+		// remove the target anger flag
+		targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
+	}
+//PGM
+
+// PMM
+// if we're healing someone, do like above and try to stay with them
+	if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
+	{
+		float	percentHealth;
+
+		percentHealth = (float)(targ->health) / (float)(targ->max_health);
+		// ignore it some of the time
+		if( targ->enemy->inuse && percentHealth > 0.25)
+			return;
+
+		// remove the medic flag
+		targ->monsterinfo.aiflags &= ~AI_MEDIC;
+		cleanupHealTarget (targ->enemy);
+	}
+// PMM
+
+	// we now know that we are not both good guys
+
+	// if attacker is a client, get mad at them because he's good and we're not
+	if (attacker->client)
+	{
+		targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+		// this can only happen in coop (both new and old enemies are clients)
+		// only switch if can't see the current enemy
+		if (targ->enemy && targ->enemy->client)
+		{
+			if (visible(targ, targ->enemy))
+			{
+				targ->oldenemy = attacker;
+				return;
+			}
+			targ->oldenemy = targ->enemy;
+		}
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ); 
+		return;
+	}
+
+	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
+	// (they spray too much), get mad at them
+	// PMM
+	// added medics to this 
+	// FIXME -
+	// this really should be turned into an AI flag marking appropriate monsters as "don't shoot me"
+	//   this also leads to the problem of tanks and medics being able to, at will, kill monsters with
+	//   no chance of retaliation.  My vote is to make those monsters who are designed as "don't shoot me"
+	//   such that they also ignore being shot by monsters as well
+	/*
+	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
+		 (strcmp (targ->classname, attacker->classname) != 0) &&
+		 (strcmp(attacker->classname, "monster_tank") != 0) &&
+		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
+		 (strcmp(attacker->classname, "monster_makron") != 0) &&
+		 (strcmp(attacker->classname, "monster_jorg") != 0) &&
+		 (strcmp(attacker->classname, "monster_carrier") != 0) && 
+ 		 (strncmp(attacker->classname, "monster_medic", 12) != 0) ) // this should get medics & medic_commanders
+	*/
+	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
+		(strcmp (targ->classname, attacker->classname) != 0) &&
+		!(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
+		!(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS) )
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+	// if they *meant* to shoot us, then shoot back
+	else if (attacker->enemy == targ)
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+	// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
+	else if (attacker->enemy && attacker->enemy != targ)
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker->enemy;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+}
+
+qboolean CheckTeamDamage (edict_t *, edict_t *)
+{
+		//FIXME make the next line real and uncomment this block
+		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
+	return false;
+}
+
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
+{
+	gclient_t	*client;
+	int			take;
+	int			save;
+	int			asave;
+	int			psave;
+	int			te_sparks;
+	int			sphere_notified;	// PGM
+
+	if (!targ->takedamage)
+		return;
+
+	sphere_notified = false;		// PGM
+
+	// friendly fire avoidance
+	// if enabled you can't hurt teammates (but you can hurt yourself)
+	// knockback still occurs
+	if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
+	{
+		if (OnSameTeam (targ, attacker))
+		{
+			// PMM - nukes kill everyone
+			if (((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) && (mod != MOD_NUKE))
+				damage = 0;
+			else
+				mod |= MOD_FRIENDLY_FIRE;
+		}
+	}
+	meansOfDeath = mod;
+
+//ROGUE
+	// allow the deathmatch game to change values
+	if (deathmatch->value && gamerules && gamerules->value)
+	{
+		if(DMGame.ChangeDamage)
+			damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
+		if(DMGame.ChangeKnockback)
+			knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
+
+		if(!damage)
+			return;
+	}
+//ROGUE
+
+	// easy mode takes half damage
+	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
+	{
+		damage *= 0.5;
+		if (!damage)
+			damage = 1;
+	}
+
+	client = targ->client;
+
+	// PMM - defender sphere takes half damage
+	if ((client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == 1))
+	{
+		damage *= 0.5;
+		if (!damage)
+			damage = 1;
+	}
+
+	if (dflags & DAMAGE_BULLET)
+		te_sparks = TE_BULLET_SPARKS;
+	else
+		te_sparks = TE_SPARKS;
+
+	VectorNormalize(dir);
+
+// bonus damage for suprising a monster
+	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
+		damage *= 2;
+
+	if (targ->flags & FL_NO_KNOCKBACK)
+		knockback = 0;
+
+// figure momentum add
+	if (!(dflags & DAMAGE_NO_KNOCKBACK))
+	{
+		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
+		{
+			vec3_t	kvel;
+			float	mass;
+
+			if (targ->mass < 50)
+				mass = 50;
+			else
+				mass = targ->mass;
+
+			if (targ->client  && attacker == targ)
+				VectorScale (dir, 1600.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
+			else
+				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
+
+			VectorAdd (targ->velocity, kvel, targ->velocity);
+		}
+	}
+
+	take = damage;
+	save = 0;
+
+	// check for godmode
+	if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
+	{
+		take = 0;
+		save = damage;
+		SpawnDamage (te_sparks, point, normal, save);
+	}
+
+	// check for invincibility
+	if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
+	{
+		if (targ->pain_debounce_time < level.time)
+		{
+			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
+			targ->pain_debounce_time = level.time + 2;
+		}
+		take = 0;
+		save = damage;
+	}
+	// ROGUE
+	// check for monster invincibility	
+	if (((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
+	{
+		if (targ->pain_debounce_time < level.time)
+		{
+			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
+			targ->pain_debounce_time = level.time + 2;
+		}
+		take = 0;
+		save = damage;
+	}
+	// ROGUE
+
+	psave = CheckPowerArmor (targ, point, normal, take, dflags);
+	take -= psave;
+
+	asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
+	take -= asave;
+
+	//treat cheat/powerup savings the same as armor
+	asave += save;
+
+	// team damage avoidance
+	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
+		return;
+
+// ROGUE - this option will do damage both to the armor and person. originally for DPU rounds
+	if (dflags & DAMAGE_DESTROY_ARMOR)
+	{
+		if(!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
+		   !(client && client->invincible_framenum > level.framenum))
+		{
+			take = damage;
+		}
+	}
+// ROGUE
+
+// do the damage
+	if (take)
+	{
+//PGM		need more blood for chainfist.
+		if(targ->flags & FL_MECHANICAL)
+		{
+			SpawnDamage ( TE_ELECTRIC_SPARKS, point, normal, take);
+		}
+		else if ((targ->svflags & SVF_MONSTER) || (client))
+		{
+			if(mod == MOD_CHAINFIST)
+				SpawnDamage (TE_MOREBLOOD, point, normal, 255);
+			else
+				SpawnDamage (TE_BLOOD, point, normal, take);
+		}
+		else
+			SpawnDamage (te_sparks, point, normal, take);
+//PGM
+
+		targ->health = targ->health - take;
+			
+//PGM - spheres need to know who to shoot at
+		if(client && client->owned_sphere)
+		{
+			sphere_notified = true;
+			if(client->owned_sphere->pain)
+				client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0);
+		}
+//PGM
+
+		if (targ->health <= 0)
+		{
+			if ((targ->svflags & SVF_MONSTER) || (client))
+				targ->flags |= FL_NO_KNOCKBACK;
+			Killed (targ, inflictor, attacker, take, point);
+			return;
+		}
+	}
+
+//PGM - spheres need to know who to shoot at
+	if (!sphere_notified)
+	{
+		if(client && client->owned_sphere)
+		{
+			if(client->owned_sphere->pain)
+				client->owned_sphere->pain (client->owned_sphere, attacker, 0, 0);
+		}
+	}
+//PGM
+
+	if (targ->svflags & SVF_MONSTER)
+	{
+		M_ReactToDamage (targ, attacker, inflictor);
+		// PMM - fixme - if anyone else but the medic ever uses AI_MEDIC, check for it here instead
+		// of in the medic's pain function
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
+		{
+			targ->pain (targ, attacker, knockback, take);
+			// nightmare mode monsters don't go into pain frames often
+			if (skill->value == 3)
+				targ->pain_debounce_time = level.time + 5;
+		}
+	}
+	else if (client)
+	{
+		if (!(targ->flags & FL_GODMODE) && (take))
+			targ->pain (targ, attacker, knockback, take);
+	}
+	else if (take)
+	{
+		if (targ->pain)
+			targ->pain (targ, attacker, knockback, take);
+	}
+
+	// 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)
+	{
+		client->damage_parmor += psave;
+		client->damage_armor += asave;
+		client->damage_blood += take;
+		client->damage_knockback += knockback;
+		VectorCopy (point, client->damage_from);
+	}
+}
+
+
+/*
+============
+T_RadiusDamage
+============
+*/
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
+{
+	float	points;
+	edict_t	*ent = NULL;
+	vec3_t	v;
+	vec3_t	dir;
+
+	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+	{
+		if (ent == ignore)
+			continue;
+		if (!ent->takedamage)
+			continue;
+
+		VectorAdd (ent->mins, ent->maxs, v);
+		VectorMA (ent->s.origin, 0.5, v, v);
+		VectorSubtract (inflictor->s.origin, v, v);
+		points = damage - 0.5 * VectorLength (v);
+		if (ent == attacker)
+			points = points * 0.5;
+		if (points > 0)
+		{
+			if (CanDamage (ent, inflictor))
+			{
+				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
+				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+			}
+		}
+	}
+}
+
+// **********************
+// ROGUE
+
+/*
+============
+T_RadiusNukeDamage
+
+Like T_RadiusDamage, but ignores walls (skips CanDamage check, among others)
+// up to KILLZONE radius, do 10,000 points
+// after that, do damage linearly out to KILLZONE2 radius
+============
+*/
+
+void T_RadiusNukeDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
+{
+	float	points;
+	edict_t	*ent = NULL;
+	vec3_t	v;
+	vec3_t	dir;
+	float	len;
+	float	killzone, killzone2;
+	trace_t	tr;
+	float	dist;
+
+	killzone = radius;
+	killzone2 = radius*2.0;
+
+	while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != NULL)
+	{
+// ignore nobody
+		if (ent == ignore)
+			continue;
+		if (!ent->takedamage)
+			continue;
+		if (!ent->inuse)
+			continue;
+		if (!(ent->client || (ent->svflags & SVF_MONSTER) || (ent->svflags & SVF_DAMAGEABLE)))
+			continue;
+
+		VectorAdd (ent->mins, ent->maxs, v);
+		VectorMA (ent->s.origin, 0.5, v, v);
+		VectorSubtract (inflictor->s.origin, v, v);
+		len = VectorLength(v);
+		if (len <= killzone)
+		{
+			if (ent->client)
+				ent->flags |= FL_NOGIB;
+			points = 10000;
+		}
+		else if (len <= killzone2)
+			points = (damage/killzone)*(killzone2 - len);
+		else
+			points = 0;
+//		points = damage - 0.005 * len*len;
+//		if (ent == attacker)
+//			points = points * 0.5;
+//		if ((g_showlogic) && (g_showlogic->value))
+//		{
+//			if (!(strcmp(ent->classname, "player")))
+//				gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, inflictor->teammaster->client->pers.netname);
+//			else
+//				gi.dprintf ("dist = %2.2f doing %6.0f damage to %s\n", len, points, ent->classname);
+//		}
+		if (points > 0)
+		{
+//			if (CanDamage (ent, inflictor))
+//			{
+				if (ent->client)
+					ent->client->nuke_framenum = level.framenum + 20;
+				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
+				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+//			}
+		}
+	}
+	ent = g_edicts+1; // skip the worldspawn
+	// cycle through players
+	while (ent)
+	{
+		if ((ent->client) && (ent->client->nuke_framenum != level.framenum+20) && (ent->inuse))
+		{
+			tr = gi.trace (inflictor->s.origin, NULL, NULL, ent->s.origin, inflictor, MASK_SOLID);
+			if (tr.fraction == 1.0)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("Undamaged player in LOS with nuke, flashing!\n");
+				ent->client->nuke_framenum = level.framenum + 20;
+			}
+			else
+			{
+				dist = realrange (ent, inflictor);
+				if (dist < 2048)
+					ent->client->nuke_framenum = qmax(ent->client->nuke_framenum,level.framenum + 15);
+				else
+					ent->client->nuke_framenum = qmax(ent->client->nuke_framenum,level.framenum + 10);
+			}
+			ent++;
+		}
+		else
+			ent = NULL;
+	}
+}
+
+/*
+============
+T_RadiusClassDamage
+
+Like T_RadiusDamage, but ignores anything with classname=ignoreClass
+============
+*/
+void T_RadiusClassDamage (edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, int mod)
+{
+	float	points;
+	edict_t	*ent = NULL;
+	vec3_t	v;
+	vec3_t	dir;
+
+	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+	{
+		if (ent->classname && !strcmp(ent->classname, ignoreClass))
+			continue;
+		if (!ent->takedamage)
+			continue;
+
+		VectorAdd (ent->mins, ent->maxs, v);
+		VectorMA (ent->s.origin, 0.5, v, v);
+		VectorSubtract (inflictor->s.origin, v, v);
+		points = damage - 0.5 * VectorLength (v);
+		if (ent == attacker)
+			points = points * 0.5;
+		if (points > 0)
+		{
+			if (CanDamage (ent, inflictor))
+			{
+				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
+				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+			}
+		}
+	}
+}
+
+// ROGUE
+// ********************
--- /dev/null
+++ b/rogue/g_func.c
@@ -1,0 +1,2829 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+=========================================================
+
+  PLATS
+
+  movement options:
+
+  linear
+  smooth start, hard stop
+  smooth start, smooth stop
+
+  start
+  end
+  acceleration
+  speed
+  deceleration
+  begin sound
+  end sound
+  target fired when reaching end
+  wait at end
+
+  object characteristics that use move segments
+  ---------------------------------------------
+  movetype_push, or movetype_stop
+  action when touched
+  action when blocked
+  action when used
+	disabled?
+  auto trigger spawning
+
+
+=========================================================
+*/
+
+#define PLAT_LOW_TRIGGER	1
+
+//====
+//PGM
+#define PLAT2_TOGGLE			2
+#define PLAT2_TOP				4
+#define PLAT2_TRIGGER_TOP		8
+#define PLAT2_TRIGGER_BOTTOM	16
+#define PLAT2_BOX_LIFT			32
+
+void plat2_spawn_danger_area (edict_t *ent);
+void plat2_kill_danger_area (edict_t *ent);
+
+//PGM
+//====
+
+#define	STATE_TOP			0
+#define	STATE_BOTTOM		1
+#define STATE_UP			2
+#define STATE_DOWN			3
+
+#define DOOR_START_OPEN		1
+#define DOOR_REVERSE		2
+#define DOOR_CRUSHER		4
+#define DOOR_NOMONSTER		8
+#define DOOR_TOGGLE			32
+#define DOOR_X_AXIS			64
+#define DOOR_Y_AXIS			128
+// !easy					256
+// !med						512
+// !hard					1024
+// !dm						2048
+// !coop					4096
+#define DOOR_INACTIVE		8192
+
+//
+// Support routines for movement (changes in origin using velocity)
+//
+
+void Move_Done (edict_t *ent)
+{
+	VectorClear (ent->velocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void Move_Final (edict_t *ent)
+{
+	if (ent->moveinfo.remaining_distance == 0)
+	{
+		Move_Done (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
+
+	ent->think = Move_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void Move_Begin (edict_t *ent)
+{
+	float	frames;
+
+	if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
+	{
+		Move_Final (ent);
+		return;
+	}
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
+	frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
+	ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
+	ent->nextthink = level.time + (frames * FRAMETIME);
+	ent->think = Move_Final;
+}
+
+void Think_AccelMove (edict_t *ent);
+
+void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
+{
+	VectorClear (ent->velocity);
+	VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
+	ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
+	ent->moveinfo.endfunc = func;
+
+	if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
+	{
+		if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+		{
+			Move_Begin (ent);
+		}
+		else
+		{
+			ent->nextthink = level.time + FRAMETIME;
+			ent->think = Move_Begin;
+		}
+	}
+	else
+	{
+		// accelerative
+		ent->moveinfo.current_speed = 0;
+		ent->think = Think_AccelMove;
+		ent->nextthink = level.time + FRAMETIME;
+	}
+}
+
+
+//
+// Support routines for angular movement (changes in angle using avelocity)
+//
+
+void AngleMove_Done (edict_t *ent)
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void AngleMove_Final (edict_t *ent)
+{
+	vec3_t	move;
+
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
+
+	if (VectorCompare (move, vec3_origin))
+	{
+		AngleMove_Done (ent);
+		return;
+	}
+
+	VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
+
+	ent->think = AngleMove_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void AngleMove_Begin (edict_t *ent)
+{
+	vec3_t	destdelta;
+	float	len;
+	float	traveltime;
+	float	frames;
+
+//PGM		accelerate as needed
+	if(ent->moveinfo.speed < ent->speed)
+	{
+		ent->moveinfo.speed += ent->accel;
+		if(ent->moveinfo.speed > ent->speed)
+			ent->moveinfo.speed = ent->speed;
+	}
+//PGM
+
+	// set destdelta to the vector needed to move
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
+	
+	// calculate length of vector
+	len = VectorLength (destdelta);
+	
+	// divide by speed to get time to reach dest
+	traveltime = len / ent->moveinfo.speed;
+
+	if (traveltime < FRAMETIME)
+	{
+		AngleMove_Final (ent);
+		return;
+	}
+
+	frames = floor(traveltime / FRAMETIME);
+
+	// scale the destdelta vector by the time spent traveling to get velocity
+	VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
+
+//PGM
+	// if we're done accelerating, act as a normal rotation
+	if(ent->moveinfo.speed >= ent->speed)
+	{
+		// set nextthink to trigger a think when dest is reached
+		ent->nextthink = level.time + frames * FRAMETIME;
+		ent->think = AngleMove_Final;
+	}
+	else
+	{
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = AngleMove_Begin;
+	}
+//PGM
+}
+
+void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc = func;
+
+//PGM
+	// if we're supposed to accelerate, this will tell anglemove_begin to do so
+	if(ent->accel != ent->speed)
+		ent->moveinfo.speed = 0;
+//PGM
+
+	if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+	{
+		AngleMove_Begin (ent);
+	}
+	else
+	{
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = AngleMove_Begin;
+	}
+}
+
+
+/*
+==============
+Think_AccelMove
+
+The team has completed a frame of movement, so
+change the speed for the next frame
+==============
+*/
+#define AccelerationDistance(target, rate)	(target * ((target / rate) + 1) / 2)
+
+void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
+{
+	float	accel_dist;
+	float	decel_dist;
+
+	moveinfo->move_speed = moveinfo->speed;
+
+	if (moveinfo->remaining_distance < moveinfo->accel)
+	{
+		moveinfo->current_speed = moveinfo->remaining_distance;
+		return;
+	}
+
+	accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
+	decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
+
+	if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
+	{
+		float	f;
+
+		f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
+		moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
+		decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
+	}
+
+	moveinfo->decel_distance = decel_dist;
+};
+
+void plat_Accelerate (moveinfo_t *moveinfo)
+{
+	// are we decelerating?
+	if (moveinfo->remaining_distance <= moveinfo->decel_distance)
+	{
+		if (moveinfo->remaining_distance < moveinfo->decel_distance)
+		{
+			if (moveinfo->next_speed)
+			{
+				moveinfo->current_speed = moveinfo->next_speed;
+				moveinfo->next_speed = 0;
+				return;
+			}
+			if (moveinfo->current_speed > moveinfo->decel)
+				moveinfo->current_speed -= moveinfo->decel;
+		}
+		return;
+	}
+
+	// are we at full speed and need to start decelerating during this move?
+	if (moveinfo->current_speed == moveinfo->move_speed)
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
+		{
+			float	p1_distance;
+			float	p2_distance;
+			float	distance;
+
+			p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+			p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
+			distance = p1_distance + p2_distance;
+			moveinfo->current_speed = moveinfo->move_speed;
+			moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+			return;
+		}
+
+	// are we accelerating?
+	if (moveinfo->current_speed < moveinfo->speed)
+	{
+		float	old_speed;
+		float	p1_distance;
+		float	p1_speed;
+		float	p2_distance;
+		float	distance;
+
+		old_speed = moveinfo->current_speed;
+
+		// figure simple acceleration up to move_speed
+		moveinfo->current_speed += moveinfo->accel;
+		if (moveinfo->current_speed > moveinfo->speed)
+			moveinfo->current_speed = moveinfo->speed;
+
+		// are we accelerating throughout this entire move?
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
+			return;
+
+		// during this move we will accelrate from current_speed to move_speed
+		// and cross over the decel_distance; figure the average speed for the
+		// entire move
+		p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+		p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
+		p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
+		distance = p1_distance + p2_distance;
+		moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
+		moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+		return;
+	}
+
+	// we are at constant velocity (move_speed)
+	return;
+};
+
+void Think_AccelMove (edict_t *ent)
+{
+	ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
+
+	// PGM 04/21/98  - this should fix sthoms' sinking drop pod. Hopefully it wont break stuff.
+//	if (ent->moveinfo.current_speed == 0)		// starting or blocked
+		plat_CalcAcceleratedMove(&ent->moveinfo);
+
+	plat_Accelerate (&ent->moveinfo);
+
+	// will the entire move complete on next frame?
+	if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
+	{
+		Move_Final (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
+	ent->nextthink = level.time + FRAMETIME;
+	ent->think = Think_AccelMove;
+}
+
+
+void plat_go_down (edict_t *ent);
+
+void plat_hit_top (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_TOP;
+
+	ent->think = plat_go_down;
+	ent->nextthink = level.time + 3;
+}
+
+void plat_hit_bottom (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_BOTTOM;
+	
+	plat2_kill_danger_area (ent);		// PGM
+}
+
+void plat_go_down (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_DOWN;
+	Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
+}
+
+void plat_go_up (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_UP;
+	Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
+	
+	plat2_spawn_danger_area(ent);	// PGM
+}
+
+void plat_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other && other->inuse)		// PGM
+			BecomeExplosion1 (other);
+		return;
+	}
+
+//PGM
+	// gib dead things
+	if(other->health < 1)
+	{
+		T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH);
+	}
+//PGM
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->moveinfo.state == STATE_UP)
+		plat_go_down (self);
+	else if (self->moveinfo.state == STATE_DOWN)
+		plat_go_up (self);
+}
+
+
+void Use_Plat (edict_t *ent, edict_t *other, edict_t *)
+{ 
+//======
+//ROGUE
+	// if a monster is using us, then allow the activity when stopped.
+	if (other->svflags & SVF_MONSTER)
+	{
+		if (ent->moveinfo.state == STATE_TOP)
+			plat_go_down (ent);
+		else if (ent->moveinfo.state == STATE_BOTTOM)
+			plat_go_up (ent);
+
+		return;
+	}
+//ROGUE
+//======
+
+	if (ent->think)
+		return;		// already down
+	plat_go_down (ent);
+}
+
+
+void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+		
+	if (other->health <= 0)
+		return;
+
+	ent = ent->enemy;	// now point at the plat, not the trigger
+	if (ent->moveinfo.state == STATE_BOTTOM)
+		plat_go_up (ent);
+	else if (ent->moveinfo.state == STATE_TOP)
+		ent->nextthink = level.time + 1;	// the player is still on the plat, so delay going down
+}
+
+// PGM - plat2's change the trigger field
+//void plat_spawn_inside_trigger (edict_t *ent)
+edict_t *plat_spawn_inside_trigger (edict_t *ent)
+{
+	edict_t	*trigger;
+	vec3_t	tmin, tmax;
+
+//
+// middle trigger
+//	
+	trigger = G_Spawn();
+	trigger->touch = Touch_Plat_Center;
+	trigger->movetype = MOVETYPE_NONE;
+	trigger->solid = SOLID_TRIGGER;
+	trigger->enemy = ent;
+	
+	tmin[0] = ent->mins[0] + 25;
+	tmin[1] = ent->mins[1] + 25;
+	tmin[2] = ent->mins[2];
+
+	tmax[0] = ent->maxs[0] - 25;
+	tmax[1] = ent->maxs[1] - 25;
+	tmax[2] = ent->maxs[2] + 8;
+
+	tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
+
+	if (ent->spawnflags & PLAT_LOW_TRIGGER)
+		tmax[2] = tmin[2] + 8;
+	
+	if (tmax[0] - tmin[0] <= 0)
+	{
+		tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
+		tmax[0] = tmin[0] + 1;
+	}
+	if (tmax[1] - tmin[1] <= 0)
+	{
+		tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
+		tmax[1] = tmin[1] + 1;
+	}
+	
+	VectorCopy (tmin, trigger->mins);
+	VectorCopy (tmax, trigger->maxs);
+
+	gi.linkentity (trigger);
+
+	return trigger;			// PGM 11/17/97
+}
+
+
+/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
+speed	default 150
+
+Plats are always drawn in the extended position, so they will light correctly.
+
+If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
+
+"speed"	overrides default 200.
+"accel" overrides default 500
+"lip"	overrides default 8 pixel lip
+
+If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
+
+Set "sounds" to one of the following:
+1) base fast
+2) chain slow
+*/
+void SP_func_plat (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+	ent->solid = SOLID_BSP;
+	ent->movetype = MOVETYPE_PUSH;
+
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = plat_blocked;
+
+	if (!ent->speed)
+		ent->speed = 20;
+	else
+		ent->speed *= 0.1;
+
+	if (!ent->accel)
+		ent->accel = 5;
+	else
+		ent->accel *= 0.1;
+
+	if (!ent->decel)
+		ent->decel = 5;
+	else
+		ent->decel *= 0.1;
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!st.lip)
+		st.lip = 8;
+
+	// pos1 is the top position, pos2 is the bottom
+	VectorCopy (ent->s.origin, ent->pos1);
+	VectorCopy (ent->s.origin, ent->pos2);
+	if (st.height)
+		ent->pos2[2] -= st.height;
+	else
+		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+
+	ent->use = Use_Plat;
+
+	plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
+
+	if (ent->targetname)
+	{
+		ent->moveinfo.state = STATE_UP;
+	}
+	else
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		gi.linkentity (ent);
+		ent->moveinfo.state = STATE_BOTTOM;
+	}
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
+}
+
+// ==========================================
+// PLAT 2
+// ==========================================
+#define PLAT2_CALLED		1
+#define PLAT2_MOVING		2
+#define PLAT2_WAITING		4
+
+void plat2_go_down (edict_t *ent);
+void plat2_go_up (edict_t *ent);
+
+void plat2_spawn_danger_area (edict_t *ent)
+{
+	vec3_t	mins, maxs;
+
+	VectorCopy(ent->mins, mins);
+	VectorCopy(ent->maxs, maxs);
+	maxs[2] = ent->mins[2] + 64;
+
+	SpawnBadArea(mins, maxs, 0, ent);
+}
+
+void plat2_kill_danger_area (edict_t *ent)
+{
+	edict_t *t;
+
+	t = NULL;
+	while ((t = G_Find (t, FOFS(classname), "bad_area")))
+	{
+		if(t->owner == ent)
+			G_FreeEdict(t);
+	}
+}
+
+void plat2_hit_top (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_TOP;
+
+	if(ent->plat2flags & PLAT2_CALLED)
+	{
+		ent->plat2flags = PLAT2_WAITING;
+		if(!(ent->spawnflags & PLAT2_TOGGLE))
+		{
+			ent->think = plat2_go_down;
+			ent->nextthink = level.time + 5.0;
+		}
+		if(deathmatch->value)
+			ent->last_move_time = level.time - 1.0;
+		else
+			ent->last_move_time = level.time - 2.0;
+	}
+	else if(!(ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE))
+	{
+		ent->plat2flags = 0;
+		ent->think = plat2_go_down;
+		ent->nextthink = level.time + 2.0;
+		ent->last_move_time = level.time;
+	}
+	else
+	{
+		ent->plat2flags = 0;
+		ent->last_move_time = level.time;
+	}
+
+	G_UseTargets (ent, ent);
+}
+
+void plat2_hit_bottom (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_BOTTOM;
+	
+	if(ent->plat2flags & PLAT2_CALLED)
+	{
+		ent->plat2flags = PLAT2_WAITING;
+		if(!(ent->spawnflags & PLAT2_TOGGLE))
+		{
+			ent->think = plat2_go_up;
+			ent->nextthink = level.time + 5.0;
+		}
+		if(deathmatch->value)
+			ent->last_move_time = level.time - 1.0;
+		else
+			ent->last_move_time = level.time - 2.0;
+	}
+	else if ((ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE))
+	{
+		ent->plat2flags = 0;
+		ent->think = plat2_go_up;
+		ent->nextthink = level.time + 2.0;
+		ent->last_move_time = level.time;
+	}
+	else
+	{
+		ent->plat2flags = 0;
+		ent->last_move_time = level.time;
+	}
+
+	plat2_kill_danger_area (ent);
+	G_UseTargets (ent, ent);
+}
+
+void plat2_go_down (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_DOWN;
+	ent->plat2flags |= PLAT2_MOVING;
+
+	Move_Calc (ent, ent->moveinfo.end_origin, plat2_hit_bottom);
+}
+
+void plat2_go_up (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_UP;
+	ent->plat2flags |= PLAT2_MOVING;
+
+	plat2_spawn_danger_area(ent);
+
+	Move_Calc (ent, ent->moveinfo.start_origin, plat2_hit_top);
+}
+
+void plat2_operate (edict_t *ent, edict_t *other)
+{
+	int		otherState;
+	float	pauseTime;
+	float	platCenter;
+	edict_t *trigger;
+
+	trigger = ent;
+	ent = ent->enemy;	// now point at the plat, not the trigger
+
+	if (ent->plat2flags & PLAT2_MOVING)
+		return;
+
+	if ((ent->last_move_time + 2) > level.time)
+		return;
+
+	platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2;
+
+	if(ent->moveinfo.state == STATE_TOP)
+	{
+		otherState = STATE_TOP;
+		if(ent->spawnflags & PLAT2_BOX_LIFT)
+		{
+			if(platCenter > other->s.origin[2])
+				otherState = STATE_BOTTOM;
+		}
+		else
+		{
+			if(trigger->absmax[2] > other->s.origin[2])
+				otherState = STATE_BOTTOM;
+		}
+	}
+	else
+	{
+		otherState = STATE_BOTTOM;
+		if(other->s.origin[2] > platCenter)
+			otherState = STATE_TOP;
+	}
+
+	ent->plat2flags = PLAT2_MOVING;
+
+	if(deathmatch->value)
+		pauseTime = 0.3;
+	else
+		pauseTime = 0.5;
+
+	if(ent->moveinfo.state != otherState)
+	{
+		ent->plat2flags |= PLAT2_CALLED;
+		pauseTime = 0.1;
+	}
+
+	ent->last_move_time = level.time;
+	
+	if(ent->moveinfo.state == STATE_BOTTOM)
+	{
+		ent->think = plat2_go_up;
+		ent->nextthink = level.time + pauseTime;
+	}
+	else
+	{
+		ent->think = plat2_go_down;
+		ent->nextthink = level.time + pauseTime;
+	}
+}
+
+void Touch_Plat_Center2 (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	// this requires monsters to actively trigger plats, not just step on them.
+
+	//FIXME - commented out for E3
+	//if (!other->client)
+	//	return;
+		
+	if (other->health <= 0)
+		return;
+
+	// PMM - don't let non-monsters activate plat2s
+	if ((!(other->svflags & SVF_MONSTER)) && (!other->client))
+		return;
+	
+	plat2_operate(ent, other);
+}
+
+void plat2_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client))
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if(other && other->inuse)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	// gib dead things
+	if(other->health < 1)
+	{
+		T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_CRUSH);
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->moveinfo.state == STATE_UP)
+		plat2_go_down (self);
+	else if (self->moveinfo.state == STATE_DOWN)
+		plat2_go_up (self);
+}
+
+void Use_Plat2 (edict_t *ent, edict_t *, edict_t *activator)
+{ 
+	edict_t		*trigger;
+	int			i;
+
+	if(ent->moveinfo.state > STATE_BOTTOM)
+		return;
+	if((ent->last_move_time + 2) > level.time)
+		return;
+
+	for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++)
+	{
+		if (!trigger->inuse)
+			continue;
+		if (trigger->touch == Touch_Plat_Center2)
+		{
+			if (trigger->enemy == ent)
+			{
+//				Touch_Plat_Center2 (trigger, activator, NULL, NULL);
+				plat2_operate (trigger, activator);
+				return;
+			}
+		} 
+	}
+}
+
+void plat2_activate (edict_t *ent, edict_t *, edict_t *)
+{
+	edict_t *trigger;
+
+//	if(ent->targetname)
+//		ent->targetname[0] = 0;
+
+	ent->use = Use_Plat2;
+
+	trigger = plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
+
+	trigger->maxs[0]+=10;
+	trigger->maxs[1]+=10;
+	trigger->mins[0]-=10;
+	trigger->mins[1]-=10;
+
+	gi.linkentity (trigger);
+	
+	trigger->touch = Touch_Plat_Center2;		// Override trigger touch function
+
+	plat2_go_down(ent);
+}
+
+/*QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_TRIGGER_TOP PLAT2_TRIGGER_BOTTOM BOX_LIFT
+speed	default 150
+
+PLAT_LOW_TRIGGER - creates a short trigger field at the bottom
+PLAT2_TOGGLE - plat will not return to default position.
+PLAT2_TOP - plat's default position will the the top.
+PLAT2_TRIGGER_TOP - plat will trigger it's targets each time it hits top
+PLAT2_TRIGGER_BOTTOM - plat will trigger it's targets each time it hits bottom
+BOX_LIFT - this indicates that the lift is a box, rather than just a platform
+
+Plats are always drawn in the extended position, so they will light correctly.
+
+If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
+
+"speed"	overrides default 200.
+"accel" overrides default 500
+"lip"	no default
+
+If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
+
+*/
+void SP_func_plat2 (edict_t *ent)
+{
+	edict_t *trigger;
+
+	VectorClear (ent->s.angles);
+	ent->solid = SOLID_BSP;
+	ent->movetype = MOVETYPE_PUSH;
+
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = plat2_blocked;
+
+	if (!ent->speed)
+		ent->speed = 20;
+	else
+		ent->speed *= 0.1;
+
+	if (!ent->accel)
+		ent->accel = 5;
+	else
+		ent->accel *= 0.1;
+
+	if (!ent->decel)
+		ent->decel = 5;
+	else
+		ent->decel *= 0.1;
+
+	if (deathmatch->value)
+	{
+		ent->speed *= 2;
+		ent->accel *= 2;
+		ent->decel *= 2;
+	}
+
+
+	//PMM Added to kill things it's being blocked by 
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+//	if (!st.lip)
+//		st.lip = 8;
+
+	// pos1 is the top position, pos2 is the bottom
+	VectorCopy (ent->s.origin, ent->pos1);
+	VectorCopy (ent->s.origin, ent->pos2);
+
+	if (st.height)
+		ent->pos2[2] -= (st.height - st.lip);
+	else
+		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+
+	ent->moveinfo.state = STATE_TOP;
+
+	if(ent->targetname)
+	{
+		ent->use = plat2_activate;
+	}
+	else
+	{
+		ent->use = Use_Plat2;
+
+		trigger = plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
+
+		// PGM - debugging??
+		trigger->maxs[0]+=10;
+		trigger->maxs[1]+=10;
+		trigger->mins[0]-=10;
+		trigger->mins[1]-=10;
+
+		gi.linkentity (trigger);
+
+		trigger->touch = Touch_Plat_Center2;		// Override trigger touch function
+
+		if(!(ent->spawnflags & PLAT2_TOP))
+		{
+			VectorCopy (ent->pos2, ent->s.origin);
+			ent->moveinfo.state = STATE_BOTTOM;
+		}	
+	}
+
+	gi.linkentity (ent);
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
+}
+
+
+//====================================================================
+
+/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL
+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.
+
+func_rotating will use it's targets when it stops and starts.
+
+"speed" determines how fast it moves; default value is 100.
+"dmg"	damage to inflict when blocked (2 default)
+"accel" if specified, is how much the rotation speed will increase per .1sec.
+
+REVERSE will cause the it to rotate in the opposite direction.
+STOP mean it will stop moving instead of pushing entities
+ACCEL means it will accelerate to it's final speed and decelerate when shutting down.
+*/
+
+//============
+//PGM
+void rotating_accel (edict_t *self)
+{
+	float	current_speed;
+
+	current_speed = VectorLength (self->avelocity);
+	if(current_speed >= (self->speed - self->accel))		// done
+	{
+		VectorScale (self->movedir, self->speed, self->avelocity);
+		G_UseTargets (self, self);
+	}
+	else
+	{
+		current_speed += self->accel;
+		VectorScale (self->movedir, current_speed, self->avelocity);
+		self->think = rotating_accel;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void rotating_decel (edict_t *self)
+{
+	float	current_speed;
+
+	current_speed = VectorLength (self->avelocity);
+	if(current_speed <= self->decel)		// done
+	{
+		VectorClear (self->avelocity);
+		G_UseTargets (self, self);
+		self->touch = NULL;
+	}
+	else
+	{
+		current_speed -= self->decel;
+		VectorScale (self->movedir, current_speed, self->avelocity);
+		self->think = rotating_decel;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+//PGM
+//============
+
+
+void rotating_blocked (edict_t *self, edict_t *other)
+{
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!VectorCompare (self->avelocity, vec3_origin))
+	{
+		self->s.sound = 0;
+//PGM
+		if(self->spawnflags & 8192)	// Decelerate
+			rotating_decel (self);
+		else
+		{
+			VectorClear (self->avelocity);
+			G_UseTargets (self, self);
+			self->touch = NULL;
+		}
+//PGM
+	}
+	else
+	{
+		self->s.sound = self->moveinfo.sound_middle;
+//PGM
+		if(self->spawnflags & 8192)	// accelerate
+			rotating_accel (self);
+		else
+		{
+			VectorScale (self->movedir, self->speed, self->avelocity);
+			G_UseTargets (self, self);
+		}
+		if (self->spawnflags & 16)
+			self->touch = rotating_touch;
+//PGM
+	}
+}
+
+void SP_func_rotating (edict_t *ent)
+{
+	ent->solid = SOLID_BSP;
+	if (ent->spawnflags & 32)
+		ent->movetype = MOVETYPE_STOP;
+	else
+		ent->movetype = MOVETYPE_PUSH;
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & 4)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & 8)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & 2)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+//	ent->moveinfo.sound_middle = "doors/hydro1.wav";
+
+	ent->use = rotating_use;
+	if (ent->dmg)
+		ent->blocked = rotating_blocked;
+
+	if (ent->spawnflags & 1)
+		ent->use (ent, NULL, NULL);
+
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 128)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+//PGM
+	if(ent->spawnflags & 8192)	// Accelerate / Decelerate
+	{
+		if(!ent->accel)
+			ent->accel = 1;
+		else if (ent->accel > ent->speed)
+			ent->accel = ent->speed;
+
+		if(!ent->decel)
+			ent->decel = 1;
+		else if (ent->decel > ent->speed)
+			ent->decel = ent->speed;
+	}
+//PGM
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+BUTTONS
+
+======================================================================
+*/
+
+/*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.
+
+"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
+"sounds"
+1) silent
+2) steam metal
+3) wooden clunk
+4) metallic click
+5) in-out
+*/
+
+void button_done (edict_t *self)
+{
+	self->moveinfo.state = STATE_BOTTOM;
+	self->s.effects &= ~EF_ANIM23;
+	self->s.effects |= EF_ANIM01;
+}
+
+void button_return (edict_t *self)
+{
+	self->moveinfo.state = STATE_DOWN;
+
+	Move_Calc (self, self->moveinfo.start_origin, button_done);
+
+	self->s.frame = 0;
+
+	if (self->health)
+		self->takedamage = DAMAGE_YES;
+}
+
+void button_wait (edict_t *self)
+{
+	self->moveinfo.state = STATE_TOP;
+	self->s.effects &= ~EF_ANIM01;
+	self->s.effects |= EF_ANIM23;
+
+	G_UseTargets (self, self->activator);
+	self->s.frame = 1;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->nextthink = level.time + self->moveinfo.wait;
+		self->think = button_return;
+	}
+}
+
+void button_fire (edict_t *self)
+{
+	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		return;
+
+	self->moveinfo.state = STATE_UP;
+	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
+		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+	Move_Calc (self, self->moveinfo.end_origin, button_wait);
+}
+
+void button_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	button_fire (self);
+}
+
+void button_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (other->health <= 0)
+		return;
+
+	self->activator = other;
+	button_fire (self);
+}
+
+void button_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->activator = attacker;
+	self->health = self->max_health;
+	self->takedamage = DAMAGE_NO;
+	button_fire (self);
+}
+
+void SP_func_button (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+	float	dist;
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_STOP;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	if (ent->sounds != 1)
+		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
+	
+	if (!ent->speed)
+		ent->speed = 40;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 4;
+
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
+
+	ent->use = button_use;
+	ent->s.effects |= EF_ANIM01;
+
+	if (ent->health)
+	{
+		ent->max_health = ent->health;
+		ent->die = button_killed;
+		ent->takedamage = DAMAGE_YES;
+	}
+	else if (! ent->targetname)
+		ent->touch = button_touch;
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+DOORS
+
+  spawn a trigger surrounding the entire team unless it is
+  already targeted by another
+
+======================================================================
+*/
+
+/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
+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
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"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)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void door_use_areaportals (edict_t *self, qboolean open)
+{
+	edict_t	*t = NULL;
+
+	if (!self->target)
+		return;
+
+	while ((t = G_Find (t, FOFS(targetname), self->target)))
+	{
+		if (cistrcmp(t->classname, "func_areaportal") == 0)
+		{
+			gi.SetAreaPortalState (t->style, open);
+		}
+	}
+}
+
+void door_go_down (edict_t *self);
+
+void door_hit_top (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_TOP;
+	if (self->spawnflags & DOOR_TOGGLE)
+		return;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->think = door_go_down;
+		self->nextthink = level.time + self->moveinfo.wait;
+	}
+}
+
+void door_hit_bottom (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_BOTTOM;
+	door_use_areaportals (self, false);
+}
+
+void door_go_down (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	if (self->max_health)
+	{
+		self->takedamage = DAMAGE_YES;
+		self->health = self->max_health;
+	}
+	
+	self->moveinfo.state = STATE_DOWN;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_bottom);
+}
+
+void door_go_up (edict_t *self, edict_t *activator)
+{
+	if (self->moveinfo.state == STATE_UP)
+		return;		// already going up
+
+	if (self->moveinfo.state == STATE_TOP)
+	{	// reset top wait time
+		if (self->moveinfo.wait >= 0)
+			self->nextthink = level.time + self->moveinfo.wait;
+		return;
+	}
+	
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	self->moveinfo.state = STATE_UP;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_top);
+
+	G_UseTargets (self, activator);
+	door_use_areaportals (self, true);
+}
+
+//======
+//PGM
+void smart_water_go_up (edict_t *self)
+{
+	float		distance;
+	edict_t		*lowestPlayer;
+	edict_t		*ent;
+	float		lowestPlayerPt;
+	int			i;
+
+	if (self->moveinfo.state == STATE_TOP)
+	{	// reset top wait time
+		if (self->moveinfo.wait >= 0)
+			self->nextthink = level.time + self->moveinfo.wait;
+		return;
+	}
+
+	if (self->health)
+	{
+		if(self->absmax[2] >= self->health)
+		{
+			VectorClear (self->velocity);
+			self->nextthink = 0;
+			self->moveinfo.state = STATE_TOP;
+			return;
+		}
+	}
+
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+
+	// find the lowest player point.
+	lowestPlayerPt = 999999;
+	lowestPlayer = NULL;
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		ent = &g_edicts[1+i];
+
+		// don't count dead or unused player slots
+		if((ent->inuse) && (ent->health > 0))
+		{
+			if (ent->absmin[2] < lowestPlayerPt)
+			{
+				lowestPlayerPt = ent->absmin[2];
+				lowestPlayer = ent;
+			}
+		}
+	}
+
+	if(!lowestPlayer)
+	{
+		return;
+	}
+
+	distance = lowestPlayerPt - self->absmax[2];
+
+	// for the calculations, make sure we intend to go up at least a little.
+	if(distance < self->accel)
+	{
+		distance = 100;
+		self->moveinfo.speed = 5;
+	}
+	else
+		self->moveinfo.speed = distance / self->accel;
+
+	if(self->moveinfo.speed < 5)
+		self->moveinfo.speed = 5;
+	else if(self->moveinfo.speed > self->speed)
+		self->moveinfo.speed = self->speed;
+
+	// FIXME - should this allow any movement other than straight up?
+	VectorSet(self->moveinfo.dir, 0, 0, 1);	
+	VectorScale (self->moveinfo.dir, self->moveinfo.speed, self->velocity);
+	self->moveinfo.remaining_distance = distance;
+
+	if(self->moveinfo.state != STATE_UP)
+	{
+		G_UseTargets (self, lowestPlayer);
+		door_use_areaportals (self, true);
+		self->moveinfo.state = STATE_UP;
+	}
+
+	self->think = smart_water_go_up;
+	self->nextthink = level.time + FRAMETIME;
+}
+//PGM
+//======
+
+void door_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*ent;
+	vec3_t	center;			//PGM
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;
+
+	if (self->spawnflags & DOOR_TOGGLE)
+	{
+		if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		{
+			// trigger all paired doors
+			for (ent = self ; ent ; ent = ent->teamchain)
+			{
+				ent->message = NULL;
+				ent->touch = NULL;
+				door_go_down (ent);
+			}
+			return;
+		}
+	}
+
+//PGM
+	// smart water is different
+	VectorAdd(self->mins, self->maxs, center);
+	VectorScale(center, 0.5, center);
+	if ((gi.pointcontents (center) & MASK_WATER) && self->spawnflags & 2)
+	{
+		self->message = NULL;
+		self->touch = NULL;
+		self->enemy = activator;
+		smart_water_go_up (self);
+		return;
+	}
+//PGM
+
+	// trigger all paired doors
+	for (ent = self ; ent ; ent = ent->teamchain)
+	{
+		ent->message = NULL;
+		ent->touch = NULL;
+		door_go_up (ent, activator);
+	}
+};
+
+void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->health <= 0)
+		return;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client))
+		return;
+
+	if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 1.0;
+
+	door_use (self->owner, other, other);
+}
+
+void Think_CalcMoveSpeed (edict_t *self)
+{
+	edict_t	*ent;
+	float	min;
+	float	time;
+	float	newspeed;
+	float	ratio;
+	float	dist;
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;		// only the team master does this
+
+	// find the smallest distance any member of the team will be moving
+	min = fabs(self->moveinfo.distance);
+	for (ent = self->teamchain; ent; ent = ent->teamchain)
+	{
+		dist = fabs(ent->moveinfo.distance);
+		if (dist < min)
+			min = dist;
+	}
+
+	time = min / self->moveinfo.speed;
+
+	// adjust speeds so they will all complete at the same time
+	for (ent = self; ent; ent = ent->teamchain)
+	{
+		newspeed = fabs(ent->moveinfo.distance) / time;
+		ratio = newspeed / ent->moveinfo.speed;
+		if (ent->moveinfo.accel == ent->moveinfo.speed)
+			ent->moveinfo.accel = newspeed;
+		else
+			ent->moveinfo.accel *= ratio;
+		if (ent->moveinfo.decel == ent->moveinfo.speed)
+			ent->moveinfo.decel = newspeed;
+		else
+			ent->moveinfo.decel *= ratio;
+		ent->moveinfo.speed = newspeed;
+	}
+}
+
+void Think_SpawnDoorTrigger (edict_t *ent)
+{
+	edict_t		*other;
+	vec3_t		mins, maxs;
+
+	if (ent->flags & FL_TEAMSLAVE)
+		return;		// only the team leader spawns a trigger
+
+	VectorCopy (ent->absmin, mins);
+	VectorCopy (ent->absmax, maxs);
+
+	for (other = ent->teamchain ; other ; other=other->teamchain)
+	{
+		AddPointToBounds (other->absmin, mins, maxs);
+		AddPointToBounds (other->absmax, mins, maxs);
+	}
+
+	// expand 
+	mins[0] -= 60;
+	mins[1] -= 60;
+	maxs[0] += 60;
+	maxs[1] += 60;
+
+	other = G_Spawn ();
+	VectorCopy (mins, other->mins);
+	VectorCopy (maxs, other->maxs);
+	other->owner = ent;
+	other->solid = SOLID_TRIGGER;
+	other->movetype = MOVETYPE_NONE;
+	other->touch = Touch_DoorTrigger;
+	gi.linkentity (other);
+
+	if (ent->spawnflags & DOOR_START_OPEN)
+		door_use_areaportals (ent, true);
+
+	Think_CalcMoveSpeed (ent);
+}
+
+void door_blocked  (edict_t *self, edict_t *other)
+{
+	edict_t	*ent;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other && other->inuse)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->spawnflags & DOOR_CRUSHER)
+		return;
+
+
+// if a door has a negative wait, it would never come back if blocked,
+// so let it just squash the object to death real fast
+	if (self->moveinfo.wait >= 0)
+	{
+		if (self->moveinfo.state == STATE_DOWN)
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_up (ent, ent->activator);
+		}
+		else
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_down (ent);
+		}
+	}
+}
+
+void door_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	edict_t	*ent;
+
+	for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+	{
+		ent->health = ent->max_health;
+		ent->takedamage = DAMAGE_NO;
+	}
+	door_use (self->teammaster, attacker, attacker);
+}
+
+void door_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 5.0;
+
+	gi.centerprintf (other, "%s", self->message);
+	gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+}
+
+void SP_func_door (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+	
+	if (!ent->speed)
+		ent->speed = 100;
+	if (deathmatch->value)
+		ent->speed *= 2;
+
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 8;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	// calculate second position
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.origin, ent->pos1);
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+}
+
+//PGM
+void Door_Activate (edict_t *self, edict_t *, edict_t *)
+{
+	self->use = NULL;
+	
+	if (self->health)
+	{
+		self->takedamage = DAMAGE_YES;
+		self->die = door_killed;
+		self->max_health = self->health;
+	}
+
+	if (self->health)
+		self->think = Think_CalcMoveSpeed;
+	else
+		self->think = Think_SpawnDoorTrigger;
+	self->nextthink = level.time + FRAMETIME;
+
+}
+//PGM
+
+/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS EASY MED HARD DM COOP INACTIVE
+TOGGLE causes the door to 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
+
+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.
+
+"distance" is how many degrees the door will be rotated.
+"speed" determines how fast the door moves; default value is 100.
+"accel" if specified,is how much the rotation speed will increase each .1 sec. (default: no accel)
+
+REVERSE will cause the door to rotate in the opposite direction.
+INACTIVE will cause the door to be inactive until triggered.
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"speed"		movement speed (100 default)
+"wait"		wait before returning (3 default, -1 = never return)
+"dmg"		damage to inflict when blocked (2 default)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void SP_func_door_rotating (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & DOOR_X_AXIS)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & DOOR_Y_AXIS)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & DOOR_REVERSE)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!st.distance)
+	{
+		gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
+		st.distance = 90;
+	}
+
+	VectorCopy (ent->s.angles, ent->pos1);
+	VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
+	ent->moveinfo.distance = st.distance;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.angles);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.angles, ent->pos1);
+		VectorNegate (ent->movedir, ent->movedir);
+	}
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	
+	if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
+	VectorCopy (ent->pos1, ent->moveinfo.start_angles);
+	VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
+	VectorCopy (ent->pos2, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+
+//PGM
+	if (ent->spawnflags & DOOR_INACTIVE)
+	{
+		ent->takedamage = DAMAGE_NO;
+		ent->die = NULL;
+		ent->think = NULL;
+		ent->nextthink = 0;
+		ent->use = Door_Activate;
+	}
+//PGM
+}
+
+void smart_water_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_LAVA);
+		// if it's still there, nuke it
+		if (other && other->inuse)		// PGM
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, 0, MOD_LAVA);
+}
+
+/*QUAKED func_water (0 .5 .8) ? START_OPEN SMART
+func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
+
+START_OPEN causes the water to move to its destination when spawned and operate in reverse.
+
+SMART causes the water to adjust its speed depending on distance to player. 
+(speed = distance/accel, min 5, max self->speed)
+"accel"		for smart water, the divisor to determine water speed. default 20 (smaller = faster)
+
+"health"	maximum height of this water brush
+"angle"		determines the opening direction (up or down only)
+"speed"		movement speed (25 default)
+"wait"		wait before returning (-1 default, -1 = TOGGLE)
+"lip"		lip remaining at end of move (0 default)
+"sounds"	(yes, these need to be changed)
+0)	no sound
+1)	water
+2)	lava
+*/
+
+void SP_func_water (edict_t *self)
+{
+	vec3_t	abs_movedir;
+
+  	G_SetMovedir (self->s.angles, self->movedir);
+	self->movetype = MOVETYPE_PUSH;
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	switch (self->sounds)
+	{
+		default:
+			break;
+
+		case 1: // water
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+
+		case 2: // lava
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+	}
+
+	// calculate second position
+	VectorCopy (self->s.origin, self->pos1);
+	abs_movedir[0] = fabs(self->movedir[0]);
+	abs_movedir[1] = fabs(self->movedir[1]);
+	abs_movedir[2] = fabs(self->movedir[2]);
+	self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
+	VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
+
+	// if it starts open, switch the positions
+	if (self->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (self->pos2, self->s.origin);
+		VectorCopy (self->pos1, self->pos2);
+		VectorCopy (self->s.origin, self->pos1);
+	}
+
+	VectorCopy (self->pos1, self->moveinfo.start_origin);
+	VectorCopy (self->s.angles, self->moveinfo.start_angles);
+	VectorCopy (self->pos2, self->moveinfo.end_origin);
+	VectorCopy (self->s.angles, self->moveinfo.end_angles);
+
+	self->moveinfo.state = STATE_BOTTOM;
+
+	if (!self->speed)
+		self->speed = 25;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
+
+	if ( self->spawnflags & 2)	// smart water
+	{
+		// this is actually the divisor of the lowest player's distance to determine speed.
+		// self->speed then becomes the cap of the speed.
+		if(!self->accel)
+			self->accel = 20;
+		self->blocked = smart_water_blocked;
+	}
+
+	if (!self->wait)
+		self->wait = -1;
+	self->moveinfo.wait = self->wait;
+
+	self->use = door_use;
+
+	if (self->wait == -1)
+		self->spawnflags |= DOOR_TOGGLE;
+
+	self->classname = "func_door";
+
+	gi.linkentity (self);
+}
+
+
+#define TRAIN_START_ON		1
+#define TRAIN_TOGGLE		2
+#define TRAIN_BLOCK_STOPS	4
+
+/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+Trains are moving platforms that players can ride.
+The targets origin specifies the min point of the train at each corner.
+The train spawns at the first target it is pointing at.
+If the train is the target of a button or trigger, it will not begin moving until activated.
+speed	default 100
+dmg		default	2
+noise	looping sound to play when the train is in motion
+
+To have other entities move with the train, set all the piece's team value to the same thing. They will move in unison.
+*/
+void train_next (edict_t *self);
+
+void train_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other && other->inuse)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+
+	if (!self->dmg)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void train_wait (edict_t *self)
+{
+	if (self->target_ent->pathtarget)
+	{
+		char	*savetarget;
+		edict_t	*ent;
+
+		ent = self->target_ent;
+		savetarget = ent->target;
+		ent->target = ent->pathtarget;
+		G_UseTargets (ent, self->activator);
+		ent->target = savetarget;
+
+		// make sure we didn't get killed by a killtarget
+		if (!self->inuse)
+			return;
+	}
+
+	if (self->moveinfo.wait)
+	{
+		if (self->moveinfo.wait > 0)
+		{
+			self->nextthink = level.time + self->moveinfo.wait;
+			self->think = train_next;
+		}
+		else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
+		{
+			// PMM - clear target_ent, let train_next get called when we get used
+//			train_next (self);
+			self->target_ent = NULL;
+			// pmm
+			self->spawnflags &= ~TRAIN_START_ON;
+			VectorClear (self->velocity);
+			self->nextthink = 0;
+		}
+
+		if (!(self->flags & FL_TEAMSLAVE))
+		{
+			if (self->moveinfo.sound_end)
+				gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+			self->s.sound = 0;
+		}
+	}
+	else
+	{
+		train_next (self);
+	}
+	
+}
+
+//PGM
+void train_piece_wait (edict_t *)
+{
+}
+//PGM
+
+void train_next (edict_t *self)
+{
+	edict_t		*ent;
+	vec3_t		dest;
+	qboolean	first;
+
+	first = true;
+again:
+	if (!self->target)
+	{
+//		gi.dprintf ("train_next: no next target\n");
+		return;
+	}
+
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_next: bad target %s\n", self->target);
+		return;
+	}
+
+	self->target = ent->target;
+
+	// check for a teleport path_corner
+	if (ent->spawnflags & 1)
+	{
+		if (!first)
+		{
+			gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
+			return;
+		}
+		first = false;
+		VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+		VectorCopy (self->s.origin, self->s.old_origin);
+		self->s.event = EV_OTHER_TELEPORT;
+		gi.linkentity (self);
+		goto again;
+	}
+
+//PGM
+	if (ent->speed)
+	{
+		self->speed = ent->speed;
+		self->moveinfo.speed = ent->speed;
+		if(ent->accel)
+			self->moveinfo.accel = ent->accel;
+		else
+			self->moveinfo.accel = ent->speed;
+		if(ent->decel)
+			self->moveinfo.decel = ent->decel;
+		else
+			self->moveinfo.decel = ent->speed;
+		self->moveinfo.current_speed = 0;
+	}
+//PGM
+
+	self->moveinfo.wait = ent->wait;
+	self->target_ent = ent;
+
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+	
+//PGM
+	if(self->team)
+	{
+		edict_t *e;
+		vec3_t	dir, dst;
+
+		VectorSubtract (dest, self->s.origin, dir);
+		for (e=self->teamchain; e ; e = e->teamchain)
+		{
+			VectorAdd(dir, e->s.origin, dst);
+			VectorCopy(e->s.origin, e->moveinfo.start_origin);
+			VectorCopy(dst, e->moveinfo.end_origin);
+
+			e->moveinfo.state = STATE_TOP;
+			e->speed = self->speed;
+			e->moveinfo.speed = self->moveinfo.speed;
+			e->moveinfo.accel = self->moveinfo.accel;
+			e->moveinfo.decel = self->moveinfo.decel;
+			e->movetype = MOVETYPE_PUSH;
+			Move_Calc (e, dst, train_piece_wait);
+		}
+	
+	}
+//PGM
+}
+
+void train_resume (edict_t *self)
+{
+	edict_t	*ent;
+	vec3_t	dest;
+
+	ent = self->target_ent;
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+}
+
+void func_train_find (edict_t *self)
+{
+	edict_t *ent;
+
+	if (!self->target)
+	{
+		gi.dprintf ("train_find: no target\n");
+		return;
+	}
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_find: target %s not found\n", self->target);
+		return;
+	}
+	self->target = ent->target;
+
+	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+	gi.linkentity (self);
+
+	// if not triggered, start immediately
+	if (!self->targetname)
+		self->spawnflags |= TRAIN_START_ON;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		self->nextthink = level.time + FRAMETIME;
+		self->think = train_next;
+		self->activator = self;
+	}
+}
+
+void train_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		if (!(self->spawnflags & TRAIN_TOGGLE))
+			return;
+		self->spawnflags &= ~TRAIN_START_ON;
+		VectorClear (self->velocity);
+		self->nextthink = 0;
+	}
+	else
+	{
+		if (self->target_ent)
+			train_resume(self);
+		else
+			train_next(self);
+	}
+}
+
+void SP_func_train (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+
+	VectorClear (self->s.angles);
+	self->blocked = train_blocked;
+	if (self->spawnflags & TRAIN_BLOCK_STOPS)
+		self->dmg = 0;
+	else
+	{
+		if (!self->dmg)
+			self->dmg = 100;
+	}
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	if (st.noise)
+		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
+
+	if (!self->speed)
+		self->speed = 100;
+
+	self->moveinfo.speed = self->speed;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
+
+	self->use = train_use;
+
+	gi.linkentity (self);
+
+	if (self->target)
+	{
+		// start trains on the second frame, to make sure their targets have had
+		// a chance to spawn
+		self->nextthink = level.time + FRAMETIME;
+		self->think = func_train_find;
+	}
+	else
+	{
+		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
+	}
+}
+
+
+/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+*/
+void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *)
+{
+	edict_t *target;
+
+	if (self->movetarget->nextthink)
+	{
+//		gi.dprintf("elevator busy\n");
+		return;
+	}
+
+	if (!other->pathtarget)
+	{
+		gi.dprintf("elevator used with no pathtarget\n");
+		return;
+	}
+
+	target = G_PickTarget (other->pathtarget);
+	if (!target)
+	{
+		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
+		return;
+	}
+
+	self->movetarget->target_ent = target;
+	train_resume (self->movetarget);
+}
+
+void trigger_elevator_init (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("trigger_elevator has no target\n");
+		return;
+	}
+	self->movetarget = G_PickTarget (self->target);
+	if (!self->movetarget)
+	{
+		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
+		return;
+	}
+	if (strcmp(self->movetarget->classname, "func_train") != 0)
+	{
+		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
+		return;
+	}
+
+	self->use = trigger_elevator_use;
+	self->svflags = SVF_NOCLIENT;
+
+}
+
+void SP_trigger_elevator (edict_t *self)
+{
+	self->think = trigger_elevator_init;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+"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)
+
+"delay"			delay before first firing when turned on, default is 0
+
+"pausetime"		additional delay used only the very first time
+				and only if spawned with START_ON
+
+These can used but not touched.
+*/
+void func_timer_think (edict_t *self)
+{
+	G_UseTargets (self, self->activator);
+	self->nextthink = level.time + self->wait + crandom() * self->random;
+}
+
+void func_timer_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	// if on, turn it off
+	if (self->nextthink)
+	{
+		self->nextthink = 0;
+		return;
+	}
+
+	// turn it on
+	if (self->delay)
+		self->nextthink = level.time + self->delay;
+	else
+		func_timer_think (self);
+}
+
+void SP_func_timer (edict_t *self)
+{
+	if (!self->wait)
+		self->wait = 1.0;
+
+	self->use = func_timer_use;
+	self->think = func_timer_think;
+
+	if (self->random >= self->wait)
+	{
+		self->random = self->wait - FRAMETIME;
+		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
+	}
+
+	if (self->spawnflags & 1)
+	{
+		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
+		self->activator = self;
+	}
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
+Conveyors are stationary brushes that move what's on them.
+The brush should be have a surface with at least one current content enabled.
+speed	default 100
+*/
+
+void func_conveyor_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & 1)
+	{
+		self->speed = 0;
+		self->spawnflags &= ~1;
+	}
+	else
+	{
+		self->speed = self->count;
+		self->spawnflags |= 1;
+	}
+
+	if (!(self->spawnflags & 2))
+		self->count = 0;
+}
+
+void SP_func_conveyor (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 100;
+
+	if (!(self->spawnflags & 1))
+	{
+		self->count = self->speed;
+		self->speed = 0;
+	}
+
+	self->use = func_conveyor_use;
+
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
+A secret door.  Slide back and then to the side.
+
+open_once		doors never closes
+1st_left		1st move is left of arrow
+1st_down		1st move is down from arrow
+always_shoot	door is shootebale even if targeted
+
+"angle"		determines the direction
+"dmg"		damage to inflic when blocked (default 2)
+"wait"		how long to hold in the open position (default 5, -1 means hold)
+*/
+
+#define SECRET_ALWAYS_SHOOT	1
+#define SECRET_1ST_LEFT		2
+#define SECRET_1ST_DOWN		4
+
+void door_secret_move1 (edict_t *self);
+void door_secret_move2 (edict_t *self);
+void door_secret_move3 (edict_t *self);
+void door_secret_move4 (edict_t *self);
+void door_secret_move5 (edict_t *self);
+void door_secret_move6 (edict_t *self);
+void door_secret_done (edict_t *self);
+
+void door_secret_use (edict_t *self, edict_t *, edict_t *)
+{
+	// make sure we're not already moving
+	if (!VectorCompare(self->s.origin, vec3_origin))
+		return;
+
+	Move_Calc (self, self->pos1, door_secret_move1);
+	door_use_areaportals (self, true);
+}
+
+void door_secret_move1 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move2;
+}
+
+void door_secret_move2 (edict_t *self)
+{
+	Move_Calc (self, self->pos2, door_secret_move3);
+}
+
+void door_secret_move3 (edict_t *self)
+{
+	if (self->wait == -1)
+		return;
+	self->nextthink = level.time + self->wait;
+	self->think = door_secret_move4;
+}
+
+void door_secret_move4 (edict_t *self)
+{
+	Move_Calc (self, self->pos1, door_secret_move5);
+}
+
+void door_secret_move5 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move6;
+}
+
+void door_secret_move6 (edict_t *self)
+{
+	Move_Calc (self, vec3_origin, door_secret_done);
+}
+
+void door_secret_done (edict_t *self)
+{
+	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		self->health = 0;
+		self->takedamage = DAMAGE_YES;
+	}
+	door_use_areaportals (self, false);
+}
+
+void door_secret_blocked  (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other && other->inuse)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void door_secret_die (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	door_secret_use (self, attacker, attacker);
+}
+
+void SP_func_door_secret (edict_t *ent)
+{
+	vec3_t	forward, right, up;
+	float	side;
+	float	width;
+	float	length;
+
+	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_secret_blocked;
+	ent->use = door_secret_use;
+
+	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		ent->health = 0;
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_secret_die;
+	}
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!ent->wait)
+		ent->wait = 5;
+
+	ent->moveinfo.accel =
+	ent->moveinfo.decel =
+	ent->moveinfo.speed = 50;
+
+	// calculate positions
+	AngleVectors (ent->s.angles, forward, right, up);
+	VectorClear (ent->s.angles);
+	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		width = fabs(DotProduct(up, ent->size));
+	else
+		width = fabs(DotProduct(right, ent->size));
+	length = fabs(DotProduct(forward, ent->size));
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
+	else
+		VectorMA (ent->s.origin, side * width, right, ent->pos1);
+	VectorMA (ent->pos1, length, forward, ent->pos2);
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->classname = "func_door";
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED func_killbox (1 0 0) ?
+Kills everything inside when fired, irrespective of protection.
+*/
+void use_killbox (edict_t *self, edict_t *, edict_t *)
+{
+	KillBox (self);
+}
+
+void SP_func_killbox (edict_t *ent)
+{
+	gi.setmodel (ent, ent->model);
+	ent->use = use_killbox;
+	ent->svflags = SVF_NOCLIENT;
+}
+
--- /dev/null
+++ b/rogue/g_items.c
@@ -1,0 +1,3213 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+qboolean	Pickup_Weapon (edict_t *ent, edict_t *other);
+void		Use_Weapon (edict_t *ent, gitem_t *inv);
+void		Drop_Weapon (edict_t *ent, gitem_t *inv);
+
+void Weapon_Blaster (edict_t *ent);
+void Weapon_Shotgun (edict_t *ent);
+void Weapon_SuperShotgun (edict_t *ent);
+void Weapon_Machinegun (edict_t *ent);
+void Weapon_Chaingun (edict_t *ent);
+void Weapon_HyperBlaster (edict_t *ent);
+void Weapon_RocketLauncher (edict_t *ent);
+void Weapon_Grenade (edict_t *ent);
+void Weapon_GrenadeLauncher (edict_t *ent);
+void Weapon_Railgun (edict_t *ent);
+void Weapon_BFG (edict_t *ent);
+
+//=========
+//Rogue Weapons
+void Weapon_ChainFist (edict_t *ent);
+void Weapon_Disintegrator (edict_t *ent);
+void Weapon_ETF_Rifle (edict_t *ent);
+void Weapon_Heatbeam (edict_t *ent);
+void Weapon_Prox (edict_t *ent);
+void Weapon_Tesla (edict_t *ent);
+void Weapon_ProxLauncher (edict_t *ent);
+//void Weapon_Nuke (edict_t *ent);
+//Rogue Weapons
+//=========
+
+gitem_armor_t jacketarmor_info	= { 25,  50, .30, .00, ARMOR_JACKET};
+gitem_armor_t combatarmor_info	= { 50, 100, .60, .30, ARMOR_COMBAT};
+gitem_armor_t bodyarmor_info	= {100, 200, .80, .60, ARMOR_BODY};
+
+static int	jacket_armor_index;
+static int	combat_armor_index;
+static int	body_armor_index;
+static int	power_screen_index;
+static int	power_shield_index;
+
+#define HEALTH_IGNORE_MAX	1
+#define HEALTH_TIMED		2
+
+void Use_Quad (edict_t *ent, gitem_t *item);
+static int	quad_drop_timeout_hack;
+
+//======================================================================
+
+/*
+===============
+GetItemByIndex
+===============
+*/
+gitem_t	*GetItemByIndex (int index)
+{
+	if (index == 0 || index >= game.num_items)
+		return NULL;
+
+	return &itemlist[index];
+}
+
+
+/*
+===============
+FindItemByClassname
+
+===============
+*/
+gitem_t	*FindItemByClassname (char *classname)
+{
+	int		i;
+	gitem_t	*it;
+
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		if (!it->classname)
+			continue;
+		if (!cistrcmp(it->classname, classname))
+			return it;
+	}
+
+	return NULL;
+}
+
+/*
+===============
+FindItem
+
+===============
+*/
+gitem_t	*FindItem (char *pickup_name)
+{
+	int		i;
+	gitem_t	*it;
+
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		if (!it->pickup_name)
+			continue;
+		if (!cistrcmp(it->pickup_name, pickup_name))
+			return it;
+	}
+
+	return NULL;
+}
+
+//======================================================================
+
+void DoRespawn (edict_t *ent)
+{
+	if (ent->team)
+	{
+		edict_t	*master;
+		int	count;
+		int choice;
+
+		master = ent->teammaster;
+
+		for (count = 0, ent = master; ent; ent = ent->chain, count++)
+			;
+
+		choice = rand() % count;
+
+		for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
+			;
+	}
+
+//=====
+//ROGUE
+	if(randomrespawn && randomrespawn->value)
+	{
+		edict_t *newEnt;
+
+		newEnt = DoRandomRespawn (ent);
+		
+		// if we've changed entities, then do some sleight of hand.
+		// otherwise, the old entity will respawn
+		if(newEnt)
+		{
+			G_FreeEdict (ent);
+			ent = newEnt;
+		}
+	}
+//ROGUE
+//=====
+
+	ent->svflags &= ~SVF_NOCLIENT;
+	ent->solid = SOLID_TRIGGER;
+	gi.linkentity (ent);
+
+	// send an effect
+	ent->s.event = EV_ITEM_RESPAWN;
+}
+
+void SetRespawn (edict_t *ent, float delay)
+{
+	ent->flags |= FL_RESPAWN;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->solid = SOLID_NOT;
+	ent->nextthink = level.time + delay;
+	ent->think = DoRespawn;
+	gi.linkentity (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Powerup (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+	if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
+		return false;
+
+	if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+		return false;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+		if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+		{
+			if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))
+				quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME;
+//PGM
+			if(ent->item->use)
+				ent->item->use (other, ent->item);
+			else
+				gi.dprintf("Powerup has no use function!\n");
+//PGM
+		}
+	}
+
+	return true;
+}
+
+void Drop_General (edict_t *ent, gitem_t *item)
+{
+	Drop_Item (ent, item);
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other)
+{
+	if (!deathmatch->value)
+		other->max_health += 1;
+
+	if (other->health < other->max_health)
+		other->health = other->max_health;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_AncientHead (edict_t *ent, edict_t *other)
+{
+	other->max_health += 2;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_Bandolier (edict_t *ent, edict_t *other)
+{
+	gitem_t	*item;
+	int		index;
+
+	if (other->client->pers.max_bullets < 250)
+		other->client->pers.max_bullets = 250;
+	if (other->client->pers.max_shells < 150)
+		other->client->pers.max_shells = 150;
+	if (other->client->pers.max_cells < 250)
+		other->client->pers.max_cells = 250;
+	if (other->client->pers.max_slugs < 75)
+		other->client->pers.max_slugs = 75;
+	//PMM
+	if (other->client->pers.max_flechettes < 250)
+		other->client->pers.max_flechettes = 250;
+#ifndef KILL_DISRUPTOR
+	if (other->client->pers.max_rounds < 150)
+		other->client->pers.max_rounds = 150;
+#endif
+	//pmm
+
+	item = FindItem("Bullets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+			other->client->pers.inventory[index] = other->client->pers.max_bullets;
+	}
+
+	item = FindItem("Shells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+			other->client->pers.inventory[index] = other->client->pers.max_shells;
+	}
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_Pack (edict_t *ent, edict_t *other)
+{
+	gitem_t	*item;
+	int		index;
+
+	if (other->client->pers.max_bullets < 300)
+		other->client->pers.max_bullets = 300;
+	if (other->client->pers.max_shells < 200)
+		other->client->pers.max_shells = 200;
+	if (other->client->pers.max_rockets < 100)
+		other->client->pers.max_rockets = 100;
+	if (other->client->pers.max_grenades < 100)
+		other->client->pers.max_grenades = 100;
+	if (other->client->pers.max_cells < 300)
+		other->client->pers.max_cells = 300;
+	if (other->client->pers.max_slugs < 100)
+		other->client->pers.max_slugs = 100;
+	//PMM
+	if (other->client->pers.max_flechettes < 200)
+		other->client->pers.max_flechettes = 200;
+#ifndef KILL_DISRUPTOR
+	if (other->client->pers.max_rounds < 200)
+		other->client->pers.max_rounds = 200;
+#endif
+	//pmm
+
+	item = FindItem("Bullets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+			other->client->pers.inventory[index] = other->client->pers.max_bullets;
+	}
+
+	item = FindItem("Shells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+			other->client->pers.inventory[index] = other->client->pers.max_shells;
+	}
+
+	item = FindItem("Cells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_cells)
+			other->client->pers.inventory[index] = other->client->pers.max_cells;
+	}
+
+	item = FindItem("Grenades");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_grenades)
+			other->client->pers.inventory[index] = other->client->pers.max_grenades;
+	}
+
+	item = FindItem("Rockets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_rockets)
+			other->client->pers.inventory[index] = other->client->pers.max_rockets;
+	}
+
+	item = FindItem("Slugs");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_slugs)
+			other->client->pers.inventory[index] = other->client->pers.max_slugs;
+	}
+//PMM
+	item = FindItem("Flechettes");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_flechettes)
+			other->client->pers.inventory[index] = other->client->pers.max_flechettes;
+	}
+#ifndef KILL_DISRUPTOR
+	item = FindItem("Rounds");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_rounds)
+			other->client->pers.inventory[index] = other->client->pers.max_rounds;
+	}
+#endif
+//pmm
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+// ================
+// PMM
+qboolean Pickup_Nuke (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+//	if (!deathmatch->value)
+//		return;
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+//	if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
+//		return false;
+
+	if (quantity >= 1)
+		return false;
+
+	if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+		return false;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+	}
+
+	return true;
+}
+
+// ================
+// PGM
+void Use_IR (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->ir_framenum > level.framenum)
+		ent->client->ir_framenum += 600;
+	else
+		ent->client->ir_framenum = level.framenum + 600;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0);
+}
+
+void Use_Double (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->double_framenum > level.framenum)
+		ent->client->double_framenum += 300;
+	else
+		ent->client->double_framenum = level.framenum + 300;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0);
+}
+
+/*
+void Use_Torch (edict_t *ent, gitem_t *item)
+{
+	ent->client->torch_framenum = level.framenum + 600;
+}
+*/
+
+void Use_Compass (edict_t *ent, gitem_t *)
+{
+	int ang;
+
+	ang = (int)(ent->client->v_angle[1]);
+	if(ang<0)
+		ang += 360;
+
+	gi.cprintf(ent, PRINT_HIGH, "Origin: %0.0f,%0.0f,%0.0f    Dir: %d\n", ent->s.origin[0], ent->s.origin[1],
+				ent->s.origin[2], ang);
+}
+
+void Use_Nuke (edict_t *ent, gitem_t *item)
+{
+	vec3_t	forward, right, start;
+	float	speed;
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorCopy (ent->s.origin, start);
+	speed = 100;
+	fire_nuke (ent, start, forward, speed);
+}
+
+void Use_Doppleganger (edict_t *ent, gitem_t *item)
+{
+	vec3_t		forward, right;
+	vec3_t		createPt, spawnPt;
+	vec3_t		ang;
+
+	VectorClear(ang);
+	ang[YAW] = ent->client->v_angle[YAW];
+	AngleVectors (ang, forward, right, NULL);
+
+	VectorMA(ent->s.origin, 48, forward, createPt);
+
+	if(!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32))
+		return;
+
+	if(!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1))
+		return;
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	SpawnGrow_Spawn (spawnPt, 0);
+	fire_doppleganger (ent, spawnPt, forward);
+}
+
+qboolean Pickup_Doppleganger (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	if(!(deathmatch->value))		// item is DM only
+		return false;
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+	if (quantity >= 1)		// FIXME - apply max to dopplegangers
+		return false;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) )
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+
+qboolean Pickup_Sphere (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	if(other->client && other->client->owned_sphere)
+	{
+//		gi.cprintf(other, PRINT_HIGH, "Only one sphere to a customer!\n");
+		return false;
+	}
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+	if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
+		return false;
+
+	if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+		return false;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+		if (((int)dmflags->value & DF_INSTANT_ITEMS))
+		{
+//PGM
+			if(ent->item->use)
+				ent->item->use (other, ent->item);
+			else
+				gi.dprintf("Powerup has no use function!\n");
+//PGM
+		}
+	}
+
+	return true;
+}
+
+void Use_Defender (edict_t *ent, gitem_t *item)
+{
+	if(ent->client && ent->client->owned_sphere)
+	{
+		gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+		return;
+	}
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	Defender_Launch (ent);
+}
+
+void Use_Hunter (edict_t *ent, gitem_t *item)
+{
+	if(ent->client && ent->client->owned_sphere)
+	{
+		gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+		return;
+	}
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	Hunter_Launch (ent);
+}
+
+void Use_Vengeance (edict_t *ent, gitem_t *item)
+{
+	if(ent->client && ent->client->owned_sphere)
+	{
+		gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n");
+		return;
+	}
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	Vengeance_Launch (ent);
+}
+
+// PGM
+// ================
+
+
+//======================================================================
+
+void Use_Quad (edict_t *ent, gitem_t *item)
+{
+	int		timeout;
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (quad_drop_timeout_hack)
+	{
+		timeout = quad_drop_timeout_hack;
+		quad_drop_timeout_hack = 0;
+	}
+	else
+	{
+		timeout = 300;
+	}
+
+	if (ent->client->quad_framenum > level.framenum)
+		ent->client->quad_framenum += timeout;
+	else
+		ent->client->quad_framenum = level.framenum + timeout;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void Use_Breather (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->breather_framenum > level.framenum)
+		ent->client->breather_framenum += 300;
+	else
+		ent->client->breather_framenum = level.framenum + 300;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void Use_Envirosuit (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->enviro_framenum > level.framenum)
+		ent->client->enviro_framenum += 300;
+	else
+		ent->client->enviro_framenum = level.framenum + 300;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void	Use_Invulnerability (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->invincible_framenum > level.framenum)
+		ent->client->invincible_framenum += 300;
+	else
+		ent->client->invincible_framenum = level.framenum + 300;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void	Use_Silencer (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+	ent->client->silencer_shots += 30;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+qboolean Pickup_Key (edict_t *ent, edict_t *other)
+{
+	if (coop->value)
+	{
+		if (strcmp(ent->classname, "key_power_cube") == 0)
+		{
+			if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8))
+				return false;
+			other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+			other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8);
+		}
+		else
+		{
+			if (other->client->pers.inventory[ITEM_INDEX(ent->item)])
+				return false;
+			other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
+		}
+		return true;
+	}
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+	return true;
+}
+
+//======================================================================
+
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count)
+{
+	int			index;
+	int			max;
+
+	if (!ent->client)
+		return false;
+
+	if (item->tag == AMMO_BULLETS)
+		max = ent->client->pers.max_bullets;
+	else if (item->tag == AMMO_SHELLS)
+		max = ent->client->pers.max_shells;
+	else if (item->tag == AMMO_ROCKETS)
+		max = ent->client->pers.max_rockets;
+	else if (item->tag == AMMO_GRENADES)
+		max = ent->client->pers.max_grenades;
+	else if (item->tag == AMMO_CELLS)
+		max = ent->client->pers.max_cells;
+	else if (item->tag == AMMO_SLUGS)
+		max = ent->client->pers.max_slugs;
+// ROGUE
+//	else if (item->tag == AMMO_MINES)
+//		max = ent->client->pers.max_mines;
+	else if (item->tag == AMMO_FLECHETTES)
+		max = ent->client->pers.max_flechettes;
+	else if (item->tag == AMMO_PROX)
+		max = ent->client->pers.max_prox;
+	else if (item->tag == AMMO_TESLA)
+		max = ent->client->pers.max_tesla;
+#ifndef KILL_DISRUPTOR
+	else if (item->tag == AMMO_DISRUPTOR)
+		max = ent->client->pers.max_rounds;
+#endif
+// ROGUE
+	else
+	{
+		gi.dprintf("undefined ammo type\n");
+		return false;
+	}
+
+	index = ITEM_INDEX(item);
+
+	if (ent->client->pers.inventory[index] == max)
+		return false;
+
+	ent->client->pers.inventory[index] += count;
+
+	if (ent->client->pers.inventory[index] > max)
+		ent->client->pers.inventory[index] = max;
+
+	return true;
+}
+
+qboolean Pickup_Ammo (edict_t *ent, edict_t *other)
+{
+	int			oldcount;
+	int			count;
+	qboolean	weapon;
+
+	weapon = (ent->item->flags & IT_WEAPON);
+	if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		count = 1000;
+	else if (ent->count)
+		count = ent->count;
+	else
+		count = ent->item->quantity;
+
+	oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+	if (!Add_Ammo (other, ent->item, count))
+		return false;
+
+	if (weapon && !oldcount)
+	{
+		// don't switch to tesla
+		if (other->client->pers.weapon != ent->item
+				&& ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster"))
+				&& (strcmp(ent->classname, "ammo_tesla")) )
+
+			other->client->newweapon = ent->item;
+	}
+
+	if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
+		SetRespawn (ent, 30);
+	return true;
+}
+
+void Drop_Ammo (edict_t *ent, gitem_t *item)
+{
+	edict_t	*dropped;
+	int		index;
+
+	index = ITEM_INDEX(item);
+	dropped = Drop_Item (ent, item);
+	if (ent->client->pers.inventory[index] >= item->quantity)
+		dropped->count = item->quantity;
+	else
+		dropped->count = ent->client->pers.inventory[index];
+
+	if (ent->client->pers.weapon && 
+		ent->client->pers.weapon->tag == AMMO_GRENADES &&
+		item->tag == AMMO_GRENADES &&
+		ent->client->pers.inventory[index] - dropped->count <= 0) {
+		gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n");
+		G_FreeEdict(dropped);
+		return;
+	}
+
+	ent->client->pers.inventory[index] -= dropped->count;
+	ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+void MegaHealth_think (edict_t *self)
+{
+	if (self->owner->health > self->owner->max_health)
+	{
+		self->nextthink = level.time + 1;
+		self->owner->health -= 1;
+		return;
+	}
+
+	if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (self, 20);
+	else
+		G_FreeEdict (self);
+}
+
+qboolean Pickup_Health (edict_t *ent, edict_t *other)
+{
+	if (!(ent->style & HEALTH_IGNORE_MAX))
+		if (other->health >= other->max_health)
+			return false;
+
+	other->health += ent->count;
+
+	// PMM - health sound fix
+	/*
+	if (ent->count == 2)
+		ent->item->pickup_sound = "items/s_health.wav";
+	else if (ent->count == 10)
+		ent->item->pickup_sound = "items/n_health.wav";
+	else if (ent->count == 25)
+		ent->item->pickup_sound = "items/l_health.wav";
+	else // (ent->count == 100)
+		ent->item->pickup_sound = "items/m_health.wav";
+	*/
+
+	if (!(ent->style & HEALTH_IGNORE_MAX))
+	{
+		if (other->health > other->max_health)
+			other->health = other->max_health;
+	}
+
+	if (ent->style & HEALTH_TIMED)
+	{
+		ent->think = MegaHealth_think;
+		ent->nextthink = level.time + 5;
+		ent->owner = other;
+		ent->flags |= FL_RESPAWN;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+	}
+	else
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+			SetRespawn (ent, 30);
+	}
+
+	return true;
+}
+
+//======================================================================
+
+int ArmorIndex (edict_t *ent)
+{
+	if (!ent->client)
+		return 0;
+
+	if (ent->client->pers.inventory[jacket_armor_index] > 0)
+		return jacket_armor_index;
+
+	if (ent->client->pers.inventory[combat_armor_index] > 0)
+		return combat_armor_index;
+
+	if (ent->client->pers.inventory[body_armor_index] > 0)
+		return body_armor_index;
+
+	return 0;
+}
+
+qboolean Pickup_Armor (edict_t *ent, edict_t *other)
+{
+	int				old_armor_index;
+	gitem_armor_t	*oldinfo;
+	gitem_armor_t	*newinfo;
+	int				newcount;
+	float			salvage;
+	int				salvagecount;
+
+	// get info on new armor
+	newinfo = (gitem_armor_t *)ent->item->info;
+
+	old_armor_index = ArmorIndex (other);
+
+	// handle armor shards specially
+	if (ent->item->tag == ARMOR_SHARD)
+	{
+		if (!old_armor_index)
+			other->client->pers.inventory[jacket_armor_index] = 2;
+		else
+			other->client->pers.inventory[old_armor_index] += 2;
+	}
+
+	// if player has no armor, just use it
+	else if (!old_armor_index)
+	{
+		other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count;
+	}
+
+	// use the better armor
+	else
+	{
+		// get info on old armor
+		if (old_armor_index == jacket_armor_index)
+			oldinfo = &jacketarmor_info;
+		else if (old_armor_index == combat_armor_index)
+			oldinfo = &combatarmor_info;
+		else // (old_armor_index == body_armor_index)
+			oldinfo = &bodyarmor_info;
+
+		if (newinfo->normal_protection > oldinfo->normal_protection)
+		{
+			// calc new armor values
+			salvage = oldinfo->normal_protection / newinfo->normal_protection;
+			salvagecount = salvage * other->client->pers.inventory[old_armor_index];
+			newcount = newinfo->base_count + salvagecount;
+			if (newcount > newinfo->max_count)
+				newcount = newinfo->max_count;
+
+			// zero count of old armor so it goes away
+			other->client->pers.inventory[old_armor_index] = 0;
+
+			// change armor to new item with computed value
+			other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
+		}
+		else
+		{
+			// calc new armor values
+			salvage = newinfo->normal_protection / oldinfo->normal_protection;
+			salvagecount = salvage * newinfo->base_count;
+			newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
+			if (newcount > oldinfo->max_count)
+				newcount = oldinfo->max_count;
+
+			// if we're already maxed out then we don't need the new armor
+			if (other->client->pers.inventory[old_armor_index] >= newcount)
+				return false;
+
+			// update current armor value
+			other->client->pers.inventory[old_armor_index] = newcount;
+		}
+	}
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, 20);
+
+	return true;
+}
+
+//======================================================================
+
+int PowerArmorType (edict_t *ent)
+{
+	if (!ent->client)
+		return POWER_ARMOR_NONE;
+
+	if (!(ent->flags & FL_POWER_ARMOR))
+		return POWER_ARMOR_NONE;
+
+	if (ent->client->pers.inventory[power_shield_index] > 0)
+		return POWER_ARMOR_SHIELD;
+
+	if (ent->client->pers.inventory[power_screen_index] > 0)
+		return POWER_ARMOR_SCREEN;
+
+	return POWER_ARMOR_NONE;
+}
+
+void Use_PowerArmor (edict_t *ent, gitem_t *)
+{
+	int		index;
+
+	if (ent->flags & FL_POWER_ARMOR)
+	{
+		ent->flags &= ~FL_POWER_ARMOR;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		index = ITEM_INDEX(FindItem("cells"));
+		if (!ent->client->pers.inventory[index])
+		{
+			gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n");
+			return;
+		}
+		ent->flags |= FL_POWER_ARMOR;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
+	}
+}
+
+qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+		// auto-use for DM only if we didn't already have one
+		if (!quantity)
+			ent->item->use (other, ent->item);
+	}
+
+	return true;
+}
+
+void Drop_PowerArmor (edict_t *ent, gitem_t *item)
+{
+	if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
+		Use_PowerArmor (ent, item);
+	Drop_General (ent, item);
+}
+
+//======================================================================
+
+/*
+===============
+Touch_Item
+===============
+*/
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	qboolean	taken;
+
+	if (!other->client)
+		return;
+	if (other->health < 1)
+		return;		// dead people can't pickup
+	if (!ent->item->pickup)
+		return;		// not a grabbable item?
+
+	taken = ent->item->pickup(ent, other);
+
+	if (taken)
+	{
+		// flash the screen
+		other->client->bonus_alpha = 0.25;	
+
+		// show icon and name on status bar
+		other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
+		other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item);
+		other->client->pickup_msg_time = level.time + 3.0;
+
+		// change selected item
+		if (ent->item->use)
+			other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);
+
+		// PMM - health sound fix
+		if (ent->item->pickup == Pickup_Health)
+		{
+			if (ent->count == 2)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0);
+			else if (ent->count == 10)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0);
+			else if (ent->count == 25)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0);
+			else // (ent->count == 100)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0);
+		}
+		else if (ent->item->pickup_sound) // PGM - paranoia
+		{
+		//
+			gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
+		}
+	}
+
+	if (!(ent->spawnflags & ITEM_TARGETS_USED))
+	{
+		G_UseTargets (ent, other);
+		ent->spawnflags |= ITEM_TARGETS_USED;
+	}
+
+	if (!taken)
+		return;
+
+	if (!((coop->value) &&  (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
+	{
+		if (ent->flags & FL_RESPAWN)
+			ent->flags &= ~FL_RESPAWN;
+		else
+			G_FreeEdict (ent);
+	}
+}
+
+//======================================================================
+
+static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	Touch_Item (ent, other, plane, surf);
+}
+
+static void drop_make_touchable (edict_t *ent)
+{
+	ent->touch = Touch_Item;
+	if (deathmatch->value)
+	{
+		ent->nextthink = level.time + 29;
+		ent->think = G_FreeEdict;
+	}
+}
+
+edict_t *Drop_Item (edict_t *ent, gitem_t *item)
+{
+	edict_t	*dropped;
+	vec3_t	forward, right;
+	vec3_t	offset;
+
+	dropped = G_Spawn();
+
+	dropped->classname = item->classname;
+	dropped->item = item;
+	dropped->spawnflags = DROPPED_ITEM;
+	dropped->s.effects = item->world_model_flags;
+	dropped->s.renderfx = RF_GLOW | RF_IR_VISIBLE;		// PGM
+	VectorSet (dropped->mins, -15, -15, -15);
+	VectorSet (dropped->maxs, 15, 15, 15);
+	gi.setmodel (dropped, dropped->item->world_model);
+	dropped->solid = SOLID_TRIGGER;
+	dropped->movetype = MOVETYPE_TOSS;  
+	dropped->touch = drop_temp_touch;
+	dropped->owner = ent;
+
+	if (ent->client)
+	{
+		trace_t	trace;
+
+		AngleVectors (ent->client->v_angle, forward, right, NULL);
+		VectorSet(offset, 24, 0, -16);
+		G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin);
+		trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs,
+			dropped->s.origin, ent, CONTENTS_SOLID);
+		VectorCopy (trace.endpos, dropped->s.origin);
+	}
+	else
+	{
+		AngleVectors (ent->s.angles, forward, right, NULL);
+		VectorCopy (ent->s.origin, dropped->s.origin);
+	}
+
+	VectorScale (forward, 100, dropped->velocity);
+	dropped->velocity[2] = 300;
+
+	dropped->think = drop_make_touchable;
+	dropped->nextthink = level.time + 1;
+
+	gi.linkentity (dropped);
+
+	return dropped;
+}
+
+void Use_Item (edict_t *ent, edict_t *, edict_t *)
+{
+	ent->svflags &= ~SVF_NOCLIENT;
+	ent->use = NULL;
+
+	if (ent->spawnflags & ITEM_NO_TOUCH)
+	{
+		ent->solid = SOLID_BBOX;
+		ent->touch = NULL;
+	}
+	else
+	{
+		ent->solid = SOLID_TRIGGER;
+		ent->touch = Touch_Item;
+	}
+
+	gi.linkentity (ent);
+}
+
+//======================================================================
+
+/*
+================
+droptofloor
+================
+*/
+void droptofloor (edict_t *ent)
+{
+	trace_t		tr;
+	vec3_t		dest;
+	float		*v;
+
+	v = tv(-15,-15,-15);
+	VectorCopy (v, ent->mins);
+	v = tv(15,15,15);
+	VectorCopy (v, ent->maxs);
+
+	if (ent->model)
+		gi.setmodel (ent, ent->model);
+	else if (ent->item->world_model)	// PGM we shouldn't need this check, but paranoia...
+		gi.setmodel (ent, ent->item->world_model);
+	ent->solid = SOLID_TRIGGER;
+	ent->movetype = MOVETYPE_TOSS;  
+	ent->touch = Touch_Item;
+
+	v = tv(0,0,-128);
+	VectorAdd (ent->s.origin, v, dest);
+
+	tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+	if (tr.startsolid)
+	{
+		gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	VectorCopy (tr.endpos, ent->s.origin);
+
+	if (ent->team)
+	{
+		ent->flags &= ~FL_TEAMSLAVE;
+		ent->chain = ent->teamchain;
+		ent->teamchain = NULL;
+
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+		if (ent == ent->teammaster)
+		{
+			ent->nextthink = level.time + FRAMETIME;
+			ent->think = DoRespawn;
+		}
+	}
+
+	if (ent->spawnflags & ITEM_NO_TOUCH)
+	{
+		ent->solid = SOLID_BBOX;
+		ent->touch = NULL;
+		ent->s.effects &= ~EF_ROTATE;
+		ent->s.renderfx &= ~RF_GLOW;
+	}
+
+	if (ent->spawnflags & ITEM_TRIGGER_SPAWN)
+	{
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+		ent->use = Use_Item;
+	}
+
+	gi.linkentity (ent);
+}
+
+
+/*
+===============
+PrecacheItem
+
+Precaches all data needed for a given item.
+This will be called for each item spawned in a level,
+and for each item in each client's inventory.
+===============
+*/
+void PrecacheItem (gitem_t *it)
+{
+	char	*s, *start;
+	char	data[MAX_QPATH];
+	int		len;
+	gitem_t	*ammo;
+
+	if (!it)
+		return;
+
+	if (it->pickup_sound)
+		gi.soundindex (it->pickup_sound);
+	if (it->world_model)
+		gi.modelindex (it->world_model);
+	if (it->view_model)
+		gi.modelindex (it->view_model);
+	if (it->icon)
+		gi.imageindex (it->icon);
+
+	// parse everything for its ammo
+	if (it->ammo && it->ammo[0])
+	{
+		ammo = FindItem (it->ammo);
+		if (ammo != it)
+			PrecacheItem (ammo);
+	}
+
+	// parse the space seperated precache string for other items
+	s = it->precaches;
+	if (!s || !s[0])
+		return;
+
+	while (*s)
+	{
+		start = s;
+		while (*s && *s != ' ')
+			s++;
+
+		len = s-start;
+		if (len >= MAX_QPATH || len < 5)
+			gi.error ("PrecacheItem: %s has bad precache string", it->classname);
+		memcpy (data, start, len);
+		data[len] = 0;
+		if (*s)
+			s++;
+
+		// determine type based on extension
+		if (!strcmp(data+len-3, "md2"))
+			gi.modelindex (data);
+		else if (!strcmp(data+len-3, "sp2"))
+			gi.modelindex (data);
+		else if (!strcmp(data+len-3, "wav"))
+			gi.soundindex (data);
+		if (!strcmp(data+len-3, "pcx"))
+			gi.imageindex (data);
+	}
+}
+
+
+//=================
+// Item_TriggeredSpawn - create the item marked for spawn creation
+//=================
+void Item_TriggeredSpawn (edict_t *self, edict_t *, edict_t *)
+{
+//	self->nextthink = level.time + 2 * FRAMETIME;    // items start after other solids
+//	self->think = droptofloor;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	if(strcmp(self->classname, "key_power_cube"))	// leave them be on key_power_cube..
+		self->spawnflags = 0;
+	droptofloor (self);
+}
+
+//=================
+// SetTriggeredSpawn - set up an item to spawn in later.
+//=================
+void SetTriggeredSpawn (edict_t *ent)
+{
+	// don't do anything on key_power_cubes.
+	if(!strcmp(ent->classname, "key_power_cube"))
+		return;
+
+	ent->think = NULL;
+	ent->nextthink = 0;
+	ent->use = Item_TriggeredSpawn;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->solid = SOLID_NOT;
+}
+
+/*
+============
+SpawnItem
+
+Sets the clipping size and plants the object on the floor.
+
+Items can't be immediately dropped to floor, because they might
+be on an entity that hasn't spawned yet.
+============
+*/
+void SpawnItem (edict_t *ent, gitem_t *item)
+{
+#ifdef KILL_DISRUPTOR
+	if ((!strcmp(ent->classname, "ammo_disruptor")) || (!strcmp(ent->classname, "weapon_disintegrator")))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+#endif
+
+// PGM - since the item may be freed by the following rules, go ahead
+//		 and move the precache until AFTER the following rules have been checked.
+//		 keep an eye on this.
+//	PrecacheItem (item);
+
+	if (ent->spawnflags > 1)		// PGM
+	{
+		if (strcmp(ent->classname, "key_power_cube") != 0)
+		{
+			ent->spawnflags = 0;
+			gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin));
+		}
+	}
+
+	// some items will be prevented in deathmatch
+	if (deathmatch->value)
+	{
+		if ( (int)dmflags->value & DF_NO_ARMOR )
+		{
+			if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_NO_ITEMS )
+		{
+			if (item->pickup == Pickup_Powerup)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+			//=====
+			//ROGUE
+			if (item->pickup  == Pickup_Sphere)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+			if (item->pickup == Pickup_Doppleganger)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+			//ROGUE
+			//=====
+		}
+		if ( (int)dmflags->value & DF_NO_HEALTH )
+		{
+			if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_INFINITE_AMMO )
+		{
+			if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) )
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+
+//==========
+//ROGUE
+		if ( (int)dmflags->value & DF_NO_MINES )
+		{
+			if ( !strcmp(ent->classname, "ammo_prox") || 
+				 !strcmp(ent->classname, "ammo_tesla") )
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_NO_NUKES )
+		{
+			if ( !strcmp(ent->classname, "ammo_nuke") )
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_NO_SPHERES )
+		{
+			if (item->pickup  == Pickup_Sphere)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+//ROGUE
+//==========
+
+	}
+//==========
+//ROGUE
+// DM only items
+	if (!deathmatch->value)
+	{
+		if (item->pickup == Pickup_Doppleganger || item->pickup == Pickup_Nuke)
+		{
+			G_FreeEdict (ent);
+			return;
+		}
+		if ((item->use == Use_Vengeance) || (item->use == Use_Hunter))
+		{
+			G_FreeEdict (ent);
+			return;
+		}
+	}
+//ROGUE
+//==========
+
+//PGM 
+	PrecacheItem (item);		
+//PGM
+
+	if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0))
+	{
+		ent->spawnflags |= (1 << (8 + level.power_cubes));
+		level.power_cubes++;
+	}
+
+	// don't let them drop items that stay in a coop game
+	if ((coop->value) && (item->flags & IT_STAY_COOP))
+	{
+		item->drop = NULL;
+	}
+
+	ent->item = item;
+	ent->nextthink = level.time + 2 * FRAMETIME;    // items start after other solids
+	ent->think = droptofloor;
+	ent->s.effects = item->world_model_flags;
+	ent->s.renderfx = RF_GLOW;
+	if (ent->model)
+		gi.modelindex (ent->model);
+
+	if (ent->spawnflags & 1)
+		SetTriggeredSpawn (ent);
+}
+
+//======================================================================
+
+gitem_t	itemlist[] = 
+{
+	{
+		NULL
+	},	// leave index 0 alone
+
+	//
+	// ARMOR
+	//
+
+/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_armor_body", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/body/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_bodyarmor",
+/* pickup */	"Body Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&bodyarmor_info,
+		ARMOR_BODY,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_armor_combat", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/combat/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_combatarmor",
+/* pickup */	"Combat Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&combatarmor_info,
+		ARMOR_COMBAT,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_armor_jacket", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/jacket/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_jacketarmor",
+/* pickup */	"Jacket Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&jacketarmor_info,
+		ARMOR_JACKET,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_armor_shard", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar2_pkup.wav",
+		"models/items/armor/shard/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_jacketarmor",
+/* pickup */	"Armor Shard",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		ARMOR_SHARD,
+/* precache */ ""
+	},
+
+
+/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_power_screen", 
+		Pickup_PowerArmor,
+		Use_PowerArmor,
+		Drop_PowerArmor,
+		NULL,
+		"misc/ar3_pkup.wav",
+		"models/items/armor/screen/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_powerscreen",
+/* pickup */	"Power Screen",
+/* width */		0,
+		60,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_power_shield",
+		Pickup_PowerArmor,
+		Use_PowerArmor,
+		Drop_PowerArmor,
+		NULL,
+		"misc/ar3_pkup.wav",
+		"models/items/armor/shield/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_powershield",
+/* pickup */	"Power Shield",
+/* width */		0,
+		60,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		0,
+/* precache */ "misc/power2.wav misc/power1.wav"
+	},
+
+
+	//
+	// WEAPONS 
+	//
+
+/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+always owned, never in the world
+*/
+	{
+		"weapon_blaster", 
+		NULL,
+		Use_Weapon,
+		NULL,
+		Weapon_Blaster,
+		"misc/w_pkup.wav",
+		NULL, 0,
+		"models/weapons/v_blast/tris.md2",
+/* icon */		"w_blaster",
+/* pickup */	"Blaster",
+		0,
+		0,
+		NULL,
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_BLASTER,
+		NULL,
+		0,
+/* precache */ "weapons/blastf1a.wav misc/lasfly.wav"
+	},
+
+/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_shotgun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Shotgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_shotg/tris.md2", EF_ROTATE,
+		"models/weapons/v_shotg/tris.md2",
+/* icon */		"w_shotgun",
+/* pickup */	"Shotgun",
+		0,
+		1,
+		"Shells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_SHOTGUN,
+		NULL,
+		0,
+/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav"
+	},
+
+/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_supershotgun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_SuperShotgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_shotg2/tris.md2", EF_ROTATE,
+		"models/weapons/v_shotg2/tris.md2",
+/* icon */		"w_sshotgun",
+/* pickup */	"Super Shotgun",
+		0,
+		2,
+		"Shells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_SUPERSHOTGUN,
+		NULL,
+		0,
+/* precache */ "weapons/sshotf1b.wav"
+	},
+
+/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_machinegun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Machinegun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_machn/tris.md2", EF_ROTATE,
+		"models/weapons/v_machn/tris.md2",
+/* icon */		"w_machinegun",
+/* pickup */	"Machinegun",
+		0,
+		1,
+		"Bullets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_MACHINEGUN,
+		NULL,
+		0,
+/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
+	},
+
+/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_chaingun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Chaingun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_chain/tris.md2", EF_ROTATE,
+		"models/weapons/v_chain/tris.md2",
+/* icon */		"w_chaingun",
+/* pickup */	"Chaingun",
+		0,
+		1,
+		"Bullets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_CHAINGUN,
+		NULL,
+		0,
+/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
+	},
+
+	// ROGUE
+/*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_etf_rifle",									// classname
+		Pickup_Weapon,										// pickup function
+		Use_Weapon,											// use function
+		Drop_Weapon,										// drop function
+		Weapon_ETF_Rifle,									// weapon think function
+		"misc/w_pkup.wav",									// pick up sound
+		"models/weapons/g_etf_rifle/tris.md2", EF_ROTATE,		// world model, world model flags
+		"models/weapons/v_etf_rifle/tris.md2",					// view model
+		"w_etf_rifle",										// icon
+		"ETF Rifle",										// name printed when picked up 
+		0,													// number of digits for statusbar
+		1,													// amount used / contained
+		"Flechettes",										// ammo type used 
+		IT_WEAPON,											// inventory flags
+		WEAP_ETFRIFLE,										// visible weapon
+		NULL,												// info (void *)
+		0,													// tag
+		"weapons/nail1.wav models/proj/flechette/tris.md2",	// precaches
+	},
+
+	// rogue
+/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_grenades",
+		Pickup_Ammo,
+		Use_Weapon,
+		Drop_Ammo,
+		Weapon_Grenade,
+		"misc/am_pkup.wav",
+		"models/items/ammo/grenades/medium/tris.md2", 0,
+		"models/weapons/v_handgr/tris.md2",
+/* icon */		"a_grenades",
+/* pickup */	"Grenades",
+/* width */		3,
+		5,
+		"grenades",
+		IT_AMMO|IT_WEAPON,
+		WEAP_GRENADES,
+		NULL,
+		AMMO_GRENADES,
+/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+	},
+
+/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_grenadelauncher",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_GrenadeLauncher,
+		"misc/w_pkup.wav",
+		"models/weapons/g_launch/tris.md2", EF_ROTATE,
+		"models/weapons/v_launch/tris.md2",
+/* icon */		"w_glauncher",
+/* pickup */	"Grenade Launcher",
+		0,
+		1,
+		"Grenades",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_GRENADELAUNCHER,
+		NULL,
+		0,
+/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
+	},
+
+	// ROGUE
+/*QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_proxlauncher",								// classname
+		Pickup_Weapon,										// pickup
+		Use_Weapon,											// use
+		Drop_Weapon,										// drop
+		Weapon_ProxLauncher,								// weapon think
+		"misc/w_pkup.wav",									// pick up sound
+		"models/weapons/g_plaunch/tris.md2", EF_ROTATE,		// world model, world model flags
+		"models/weapons/v_plaunch/tris.md2",				// view model
+		"w_proxlaunch",										// icon
+		"Prox Launcher",									// name printed when picked up
+		0,													// number of digits for statusbar
+		1,													// amount used
+		"Prox",												// ammo type used
+		IT_WEAPON,											// inventory flags
+		WEAP_PROXLAUNCH,									// visible weapon
+		NULL,												// info (void *)
+		AMMO_PROX,											// tag
+		"weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav",
+	},
+	// rogue
+
+/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_rocketlauncher",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_RocketLauncher,
+		"misc/w_pkup.wav",
+		"models/weapons/g_rocket/tris.md2", EF_ROTATE,
+		"models/weapons/v_rocket/tris.md2",
+/* icon */		"w_rlauncher",
+/* pickup */	"Rocket Launcher",
+		0,
+		1,
+		"Rockets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_ROCKETLAUNCHER,
+		NULL,
+		0,
+/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
+	},
+
+/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_hyperblaster", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_HyperBlaster,
+		"misc/w_pkup.wav",
+		"models/weapons/g_hyperb/tris.md2", EF_ROTATE,
+		"models/weapons/v_hyperb/tris.md2",
+/* icon */		"w_hyperblaster",
+/* pickup */	"HyperBlaster",
+		0,
+		1,
+		"Cells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_HYPERBLASTER,
+		NULL,
+		0,
+/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
+	},
+
+	// ROGUE
+/*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/ 
+	{
+		"weapon_plasmabeam",								// classname
+		Pickup_Weapon,										// pickup function
+		Use_Weapon,											// use function
+		Drop_Weapon,										// drop function
+		Weapon_Heatbeam,									// weapon think function
+		"misc/w_pkup.wav",									// pick up sound
+		"models/weapons/g_beamer/tris.md2", EF_ROTATE,		// world model, world model flags
+		"models/weapons/v_beamer/tris.md2",					// view model
+		"w_heatbeam",											// icon
+		"Plasma Beam",											// name printed when picked up 
+		0,													// number of digits for statusbar
+		// FIXME - if this changes, change it in NoAmmoWeaponChange as well
+		2,													// amount used / contained
+		"Cells",											// ammo type used 
+		IT_WEAPON,											// inventory flags
+		WEAP_PLASMA,										// visible weapon
+		NULL,												// info (void *)
+		0,													// tag
+		"models/weapons/v_beamer2/tris.md2 weapons/bfg__l1a.wav",		// precaches
+	},
+	//rogue
+/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_railgun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Railgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_rail/tris.md2", EF_ROTATE,
+		"models/weapons/v_rail/tris.md2",
+/* icon */		"w_railgun",
+/* pickup */	"Railgun",
+		0,
+		1,
+		"Slugs",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_RAILGUN,
+		NULL,
+		0,
+/* precache */ "weapons/rg_hum.wav"
+	},
+
+/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_bfg",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_BFG,
+		"misc/w_pkup.wav",
+		"models/weapons/g_bfg/tris.md2", EF_ROTATE,
+		"models/weapons/v_bfg/tris.md2",
+/* icon */		"w_bfg",
+/* pickup */	"BFG10K",
+		0,
+		50,
+		"Cells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_BFG,
+		NULL,
+		0,
+/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
+	},
+
+// =========================
+// ROGUE WEAPONS
+/*QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_chainfist",									// classname
+		Pickup_Weapon,										// pickup function
+		Use_Weapon,											// use function
+		Drop_Weapon,										// drop function
+		Weapon_ChainFist,									// weapon think function
+		"misc/w_pkup.wav",									// pick up sound
+		"models/weapons/g_chainf/tris.md2", EF_ROTATE,		// world model, world model flags
+		"models/weapons/v_chainf/tris.md2",					// view model
+		"w_chainfist",										// icon
+		"Chainfist",										// name printed when picked up 
+		0,													// number of digits for statusbar
+		0,													// amount used / contained
+		NULL,												// ammo type used 
+		IT_WEAPON | IT_MELEE,								// inventory flags
+		WEAP_CHAINFIST,										// visible weapon
+		NULL,												// info (void *)
+		1,													// tag
+		"weapons/sawidle.wav weapons/sawhit.wav",			// precaches
+	},
+
+/*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"weapon_disintegrator",								// classname
+		Pickup_Weapon,										// pickup function
+		Use_Weapon,											// use function
+		Drop_Weapon,										// drop function
+		Weapon_Disintegrator,								// weapon think function
+		"misc/w_pkup.wav",									// pick up sound
+		"models/weapons/g_dist/tris.md2", EF_ROTATE,		// world model, world model flags
+		"models/weapons/v_dist/tris.md2",					// view model
+		"w_disintegrator",									// icon
+		"Disruptor",										// name printed when picked up 
+		0,													// number of digits for statusbar
+		1,													// amount used / contained
+		"Rounds",											// ammo type used 
+#ifdef KILL_DISRUPTOR
+		IT_NOT_GIVEABLE,
+#else
+		IT_WEAPON,											// inventory flags
+#endif
+		WEAP_DISRUPTOR,										// visible weapon
+		NULL,												// info (void *)
+		1,													// tag
+		"models/items/spawngro/tris.md2 models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav",	// precaches
+	},
+
+// ROGUE WEAPONS
+// =========================
+
+
+	//
+	// AMMO ITEMS
+	//
+
+/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_shells",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/shells/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_shells",
+/* pickup */	"Shells",
+/* width */		3,
+		10,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_SHELLS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_bullets",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/bullets/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_bullets",
+/* pickup */	"Bullets",
+/* width */		3,
+		50,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_BULLETS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_cells",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/cells/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_cells",
+/* pickup */	"Cells",
+/* width */		3,
+		50,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_CELLS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_rockets",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/rockets/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_rockets",
+/* pickup */	"Rockets",
+/* width */		3,
+		5,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_ROCKETS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_slugs",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/slugs/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_slugs",
+/* pickup */	"Slugs",
+/* width */		3,
+		10,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_SLUGS,
+/* precache */ ""
+	},
+
+// =======================================
+// ROGUE AMMO
+
+/*QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_flechettes",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/ammo/am_flechette/tris.md2", 0,
+		NULL,
+		"a_flechettes",
+		"Flechettes",
+		3,
+		50,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_FLECHETTES
+	},
+/*QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_prox",										// Classname
+		Pickup_Ammo,										// pickup function
+		NULL,												// use function
+		Drop_Ammo,											// drop function
+		NULL,												// weapon think function
+		"misc/am_pkup.wav",									// pickup sound
+		"models/ammo/am_prox/tris.md2", 0,					// world model, world model flags
+		NULL,												// view model
+		"a_prox",											// icon
+		"Prox",												// Name printed when picked up
+		3,													// number of digits for status bar
+		5,													// amount contained
+		NULL,												// ammo type used
+		IT_AMMO,											// inventory flags
+		0,													// vwep index
+		NULL,												// info (void *)
+		AMMO_PROX,											// tag
+		"models/weapons/g_prox/tris.md2 weapons/proxwarn.wav"	// precaches
+	},
+
+/*QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_tesla",
+		Pickup_Ammo,
+		Use_Weapon,						// PGM
+		Drop_Ammo,
+		Weapon_Tesla,					// PGM
+		"misc/am_pkup.wav",
+//		"models/weapons/g_tesla/tris.md2", 0,
+		"models/ammo/am_tesl/tris.md2", 0,
+		"models/weapons/v_tesla/tris.md2",
+		"a_tesla",
+		"Tesla",
+		3,
+		5,
+		"Tesla",												// PGM
+		IT_AMMO | IT_WEAPON,						// inventory flags
+		0,
+		NULL,										// info (void *)
+		AMMO_TESLA,									// tag
+		"models/weapons/v_tesla2/tris.md2 weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2"			// precache
+	},
+
+/*QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_nuke",
+		Pickup_Nuke,
+		Use_Nuke,						// PMM
+		Drop_Ammo,
+		NULL,							// PMM
+		"misc/am_pkup.wav",
+		"models/weapons/g_nuke/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_nuke",
+/* pickup */	"A-M Bomb",
+/* width */		3,
+		300, /* quantity (used for respawn time) */
+		"A-M Bomb",
+		IT_POWERUP,	
+		0,
+		NULL,
+		0,
+		"weapons/nukewarn2.wav world/rumble.wav"
+	},
+
+/*QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"ammo_disruptor",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/ammo/am_disr/tris.md2", 0,
+		NULL,
+		"a_disruptor",
+		"Rounds",		// FIXME 
+		3,
+		15,
+		NULL,
+#ifdef KILL_DISRUPTOR
+		IT_NOT_GIVEABLE,
+#else
+		IT_AMMO,											// inventory flags
+#endif
+		0,
+		NULL,
+#ifdef KILL_DISRUPTOR
+		0,
+#else
+		AMMO_DISRUPTOR,
+#endif
+	},
+// ROGUE AMMO
+// =======================================
+
+	//
+	// POWERUP ITEMS
+	//
+/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_quad", 
+		Pickup_Powerup,
+		Use_Quad,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/quaddama/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_quad",
+/* pickup */	"Quad Damage",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav"
+	},
+
+/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_invulnerability",
+		Pickup_Powerup,
+		Use_Invulnerability,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/invulner/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_invulnerability",
+/* pickup */	"Invulnerability",
+/* width */		2,
+		300,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav"
+	},
+
+/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_silencer",
+		Pickup_Powerup,
+		Use_Silencer,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/silencer/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_silencer",
+/* pickup */	"Silencer",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_breather",
+		Pickup_Powerup,
+		Use_Breather,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/breather/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_rebreather",
+/* pickup */	"Rebreather",
+/* width */		2,
+		60,
+		NULL,
+		IT_STAY_COOP|IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/airout.wav"
+	},
+
+/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_enviro",
+		Pickup_Powerup,
+		Use_Envirosuit,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/enviro/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_envirosuit",
+/* pickup */	"Environment Suit",
+/* width */		2,
+		60,
+		NULL,
+		IT_STAY_COOP|IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/airout.wav"
+	},
+
+/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+Special item that gives +2 to maximum health
+*/
+	{
+		"item_ancient_head",
+		Pickup_AncientHead,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/c_head/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_fixme",
+/* pickup */	"Ancient Head",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+gives +1 to maximum health
+*/
+	{
+		"item_adrenaline",
+		Pickup_Adrenaline,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/adrenal/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_adrenaline",
+/* pickup */	"Adrenaline",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_bandolier",
+		Pickup_Bandolier,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/band/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_bandolier",
+/* pickup */	"Bandolier",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_pack",
+		Pickup_Pack,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/pack/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_pack",
+/* pickup */	"Ammo Pack",
+/* width */		2,
+		180,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+// ======================================
+// PGM
+
+/*QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+gives +1 to maximum health
+*/
+	{
+		"item_ir_goggles",
+		Pickup_Powerup,
+		Use_IR,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/goggles/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_ir",
+/* pickup */	"IR Goggles",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "misc/ir_start.wav"
+	},
+
+/*QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_double", 
+		Pickup_Powerup,
+		Use_Double,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/ddamage/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_double",
+/* pickup */	"Double Damage",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav"
+	},
+
+/*Q U A K E D item_torch (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+/*
+	{
+		"item_torch", 
+		Pickup_Powerup,
+		Use_Torch,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/objects/fire/tris.md2", EF_ROTATE,
+		NULL,
+		"p_torch",
+		"torch",
+		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+	},
+*/
+
+/*QUAKED item_compass (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_compass", 
+		Pickup_Powerup,
+		Use_Compass,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/objects/fire/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_compass",
+/* pickup */	"compass",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+	},
+
+/*QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_sphere_vengeance", 
+		Pickup_Sphere,
+		Use_Vengeance,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/vengnce/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_vengeance",
+/* pickup */	"vengeance sphere",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+		"spheres/v_idle.wav"		//precache
+	},
+
+/*QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_sphere_hunter", 
+		Pickup_Sphere,
+		Use_Hunter,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/hunter/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_hunter",
+/* pickup */	"hunter sphere",
+/* width */		2,
+		120,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+		"spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav"		//precache
+	},
+
+/*QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_sphere_defender", 
+		Pickup_Sphere,
+		Use_Defender,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/defender/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_defender",
+/* pickup */	"defender sphere",
+/* width */		2,
+		60,													// respawn time
+		NULL,												// ammo type used
+		IT_POWERUP,											// inventory flags
+		0,
+		NULL,												// info (void *)
+		0,													// tag
+		"models/proj/laser2/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav"		// precache
+	},
+
+/*QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"item_doppleganger",								// classname
+		Pickup_Doppleganger,								// pickup function
+		Use_Doppleganger,									// use function
+		Drop_General,										// drop function
+		NULL,												// weapon think function
+		"items/pkup.wav",									// pick up sound
+		"models/items/dopple/tris.md2",						// world model
+		EF_ROTATE,											// world model flags
+		NULL,												// view model
+		"p_doppleganger",									// icon
+		"Doppleganger",										// name printed when picked up 
+		0,													// number of digits for statusbar
+		90,													// respawn time
+		NULL,												// ammo type used 
+		IT_POWERUP,											// inventory flags
+		0,
+		NULL,												// info (void *)
+		0,													// tag
+		"models/objects/dopplebase/tris.md2 models/items/spawngro2/tris.md2 models/items/hunter/tris.md2 models/items/vengnce/tris.md2",		// precaches
+	},
+
+	{
+//		"dm_tag_token",										// classname
+		NULL,												// classname
+		Tag_PickupToken,									// pickup function
+		NULL,												// use function
+		NULL,												// drop function
+		NULL,												// weapon think function
+		"items/pkup.wav",									// pick up sound
+		"models/items/tagtoken/tris.md2",					// world model
+		EF_ROTATE | EF_TAGTRAIL,							// world model flags
+		NULL,												// view model
+		"i_tagtoken",										// icon
+		"Tag Token",										// name printed when picked up 
+		0,													// number of digits for statusbar
+		0,													// amount used / contained
+		NULL,												// ammo type used 
+		IT_POWERUP | IT_NOT_GIVEABLE,						// inventory flags
+		0,
+		NULL,												// info (void *)
+		1,													// tag
+		NULL,												// precaches
+	},
+
+// PGM
+// ======================================
+
+
+	//
+	// KEYS
+	//
+/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+key for computer centers
+*/
+	{
+		"key_data_cd",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/data_cd/tris.md2", EF_ROTATE,
+		NULL,
+		"k_datacd",
+		"Data CD",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
+warehouse circuits
+*/
+	{
+		"key_power_cube",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/power/tris.md2", EF_ROTATE,
+		NULL,
+		"k_powercube",
+		"Power Cube",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+key for the entrance of jail3
+*/
+	{
+		"key_pyramid",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/pyramid/tris.md2", EF_ROTATE,
+		NULL,
+		"k_pyramid",
+		"Pyramid Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+key for the city computer
+*/
+	{
+		"key_data_spinner",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/spinner/tris.md2", EF_ROTATE,
+		NULL,
+		"k_dataspin",
+		"Data Spinner",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+security pass for the security level
+*/
+	{
+		"key_pass",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/pass/tris.md2", EF_ROTATE,
+		NULL,
+		"k_security",
+		"Security Pass",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+normal door key - blue
+*/
+	{
+		"key_blue_key",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/key/tris.md2", EF_ROTATE,
+		NULL,
+		"k_bluekey",
+		"Blue Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+normal door key - red
+*/
+	{
+		"key_red_key",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/red_key/tris.md2", EF_ROTATE,
+		NULL,
+		"k_redkey",
+		"Red Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+tank commander's head
+*/
+	{
+		"key_commander_head",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/monsters/commandr/head/tris.md2", EF_GIB,
+		NULL,
+/* icon */		"k_comhead",
+/* pickup */	"Commander's Head",
+/* width */		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+tank commander's head
+*/
+	{
+		"key_airstrike_target",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/target/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_airstrike",
+/* pickup */	"Airstrike Marker",
+/* width */		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+// ======================================
+// PGM
+
+/*QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"key_nuke_container",								// classname
+		Pickup_Key,											// pickup function
+		NULL,												// use function
+		Drop_General,										// drop function
+		NULL,												// weapon think function
+		"items/pkup.wav",									// pick up sound
+		"models/weapons/g_nuke/tris.md2",					// world model
+		EF_ROTATE,											// world model flags
+		NULL,												// view model
+		"i_contain",										// icon
+		"Antimatter Pod",									// name printed when picked up 
+		2,													// number of digits for statusbar
+		0,													// respawn time
+		NULL,												// ammo type used 
+		IT_STAY_COOP|IT_KEY,								// inventory flags
+		0,
+		NULL,												// info (void *)
+		0,													// tag
+		NULL,												// precaches
+	},
+
+/*QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+	{
+		"key_nuke",											// classname
+		Pickup_Key,											// pickup function
+		NULL,												// use function
+		Drop_General,										// drop function
+		NULL,												// weapon think function
+		"items/pkup.wav",									// pick up sound
+		"models/weapons/g_nuke/tris.md2",					// world model
+		EF_ROTATE,											// world model flags
+		NULL,												// view model
+		"i_nuke",											// icon
+		"Antimatter Bomb",									// name printed when picked up 
+		2,													// number of digits for statusbar
+		0,													// respawn time
+		NULL,												// ammo type used 
+		IT_STAY_COOP|IT_KEY,								// inventory flags
+		0,
+		NULL,												// info (void *)
+		0,													// tag
+		NULL,												// precaches
+	},
+
+// PGM
+// ======================================
+
+	{
+		NULL,
+		Pickup_Health,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		NULL, 0,
+		NULL,
+/* icon */		"i_health",
+/* pickup */	"Health",
+/* width */		3,
+		0,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"  // PMM - health sound fix
+	},
+
+	// end of list marker
+	{NULL}
+};
+
+
+/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+void SP_item_health (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/medium/tris.md2";
+	self->count = 10;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/n_health.wav");
+}
+
+/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+void SP_item_health_small (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/stimpack/tris.md2";
+	self->count = 2;
+	SpawnItem (self, FindItem ("Health"));
+	self->style = HEALTH_IGNORE_MAX;
+	gi.soundindex ("items/s_health.wav");
+}
+
+/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+void SP_item_health_large (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/large/tris.md2";
+	self->count = 25;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/l_health.wav");
+}
+
+/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN
+*/
+void SP_item_health_mega (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/mega_h/tris.md2";
+	self->count = 100;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/m_health.wav");
+	self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED;
+}
+
+
+void InitItems (void)
+{
+	game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1;
+}
+
+
+
+/*
+===============
+SetItemNames
+
+Called by worldspawn
+===============
+*/
+void SetItemNames (void)
+{
+	int		i;
+	gitem_t	*it;
+
+	for (i=0 ; i<game.num_items ; i++)
+	{
+		it = &itemlist[i];
+		gi.configstring (CS_ITEMS+i, it->pickup_name);
+	}
+
+	jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
+	combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
+	body_armor_index   = ITEM_INDEX(FindItem("Body Armor"));
+	power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
+	power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
+}
+
+
+//===============
+//ROGUE
+void SP_xatrix_item (edict_t *self)
+{
+	gitem_t	*item;
+	int		i;
+	char	*spawnClass = nil;
+
+	if(!self->classname)
+		return;
+
+	if(!strcmp(self->classname, "ammo_magslug"))
+		spawnClass = "ammo_flechettes";
+	else if(!strcmp(self->classname, "ammo_trap"))
+		spawnClass = "weapon_proxlauncher";
+	else if(!strcmp(self->classname, "item_quadfire"))
+	{
+		float	chance;
+
+		chance = qrandom();
+		if(chance < 0.2)
+			spawnClass = "item_sphere_hunter";
+		else if (chance < 0.6)
+			spawnClass = "item_sphere_vengeance";
+		else
+			spawnClass = "item_sphere_defender";
+	}
+	else if(!strcmp(self->classname, "weapon_boomer"))
+		spawnClass = "weapon_etf_rifle";
+	else if(!strcmp(self->classname, "weapon_phalanx"))
+		spawnClass = "weapon_plasmabeam";
+
+
+	// check item spawn functions
+	for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
+	{
+		if (!item->classname)
+			continue;
+		if (!strcmp(item->classname, spawnClass))
+		{	// found it
+			SpawnItem (self, item);
+			return;
+		}
+	}
+}
+//ROGUE
+//===============
--- /dev/null
+++ b/rogue/g_main.c
@@ -1,0 +1,384 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+game_locals_t	game;
+level_locals_t	level;
+game_import_t	gi;
+game_export_t	globals;
+spawn_temp_t	st;
+
+int	sm_meat_index;
+int	snd_fry;
+int meansOfDeath;
+
+edict_t		*g_edicts;
+
+cvar_t	*deathmatch;
+cvar_t	*coop;
+cvar_t	*dmflags;
+cvar_t	*skill;
+cvar_t	*fraglimit;
+cvar_t	*timelimit;
+cvar_t	*password;
+cvar_t	*spectator_password;
+cvar_t	*maxclients;
+cvar_t	*maxspectators;
+cvar_t	*maxentities;
+cvar_t	*g_select_empty;
+cvar_t	*dedicated;
+
+cvar_t	*filterban;
+
+cvar_t	*sv_maxvelocity;
+cvar_t	*sv_gravity;
+
+cvar_t	*sv_rollspeed;
+cvar_t	*sv_rollangle;
+cvar_t	*gun_x;
+cvar_t	*gun_y;
+cvar_t	*gun_z;
+
+cvar_t	*run_pitch;
+cvar_t	*run_roll;
+cvar_t	*bob_up;
+cvar_t	*bob_pitch;
+cvar_t	*bob_roll;
+
+cvar_t	*sv_cheats;
+
+cvar_t	*flood_msgs;
+cvar_t	*flood_persecond;
+cvar_t	*flood_waitdelay;
+
+cvar_t	*sv_maplist;
+
+cvar_t	*sv_stopspeed;	//PGM	 (this was a define in g_phys.c)
+
+//ROGUE cvars
+cvar_t	*g_showlogic;
+cvar_t	*gamerules;
+cvar_t	*huntercam;
+cvar_t	*strong_mines;
+cvar_t	*randomrespawn;
+//ROGUE
+
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
+void ClientThink (edict_t *ent, usercmd_t *cmd);
+qboolean ClientConnect (edict_t *ent, char *userinfo);
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+void ClientDisconnect (edict_t *ent);
+void ClientBegin (edict_t *ent);
+void ClientCommand (edict_t *ent);
+void RunEntity (edict_t *ent);
+void WriteGame (char *filename, qboolean autosave);
+void ReadGame (char *filename);
+void WriteLevel (char *filename);
+void ReadLevel (char *filename);
+void InitGame (void);
+void G_RunFrame (void);
+
+
+//===================================================================
+
+
+void ShutdownGame (void)
+{
+	gi.dprintf ("==== ShutdownGame ====\n");
+
+	gi.FreeTags (TAG_LEVEL);
+	gi.FreeTags (TAG_GAME);
+}
+
+
+/*
+=================
+GetGameAPI
+
+Returns a pointer to the structure with all entry points
+and global variables
+=================
+*/
+game_export_t *GetGameAPI (game_import_t *import)
+{
+	gi = *import;
+
+	globals.apiversion = GAME_API_VERSION;
+	globals.Init = InitGame;
+	globals.Shutdown = ShutdownGame;
+	globals.SpawnEntities = SpawnEntities;
+
+	globals.WriteGame = WriteGame;
+	globals.ReadGame = ReadGame;
+	globals.WriteLevel = WriteLevel;
+	globals.ReadLevel = ReadLevel;
+
+	globals.ClientThink = ClientThink;
+	globals.ClientConnect = ClientConnect;
+	globals.ClientUserinfoChanged = ClientUserinfoChanged;
+	globals.ClientDisconnect = ClientDisconnect;
+	globals.ClientBegin = ClientBegin;
+	globals.ClientCommand = ClientCommand;
+
+	globals.RunFrame = G_RunFrame;
+
+	globals.ServerCommand = ServerCommand;
+
+	globals.edict_size = sizeof(edict_t);
+
+	return &globals;
+}
+
+/*
+=================
+ClientEndServerFrames
+=================
+*/
+void ClientEndServerFrames (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	// calc the player views now that all pushing
+	// and damage has been added
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse || !ent->client)
+			continue;
+		ClientEndServerFrame (ent);
+	}
+
+}
+
+/*
+=================
+CreateTargetChangeLevel
+
+Returns the created target changelevel
+=================
+*/
+edict_t *CreateTargetChangeLevel(char *map)
+{
+	edict_t *ent;
+
+	ent = G_Spawn ();
+	ent->classname = "target_changelevel";
+	Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
+	ent->map = level.nextmap;
+	return ent;
+}
+
+/*
+=================
+EndDMLevel
+
+The timelimit or fraglimit has been exceeded
+=================
+*/
+void EndDMLevel (void)
+{
+	edict_t		*ent;
+	char *s, *t, *f;
+	static const char *seps = " ,\n\r";
+
+	// stay on same level flag
+	if ((int)dmflags->value & DF_SAME_LEVEL)
+	{
+		BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+		return;
+	}
+
+	// see if it's in the map list
+	if (*sv_maplist->string) {
+		s = strdup(sv_maplist->string);
+		f = NULL;
+		t = strtok(s, seps);
+		while (t != NULL) {
+			if (cistrcmp(t, level.mapname) == 0) {
+				// it's in the list, go to the next one
+				t = strtok(NULL, seps);
+				if (t == NULL) { // end of list, go to first one
+					if (f == NULL) // there isn't a first one, same level
+						BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+					else
+						BeginIntermission (CreateTargetChangeLevel (f) );
+				} else
+					BeginIntermission (CreateTargetChangeLevel (t) );
+				free(s);
+				return;
+			}
+			if (!f)
+				f = t;
+			t = strtok(NULL, seps);
+		}
+		free(s);
+	}
+
+	if (level.nextmap[0]) // go to a specific map
+		BeginIntermission (CreateTargetChangeLevel (level.nextmap) );
+	else {	// search for a changelevel
+		ent = G_Find (NULL, FOFS(classname), "target_changelevel");
+		if (!ent)
+		{	// the map designer didn't include a changelevel,
+			// so create a fake ent that goes back to the same level
+			BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+			return;
+		}
+		BeginIntermission (ent);
+	}
+}
+
+/*
+=================
+CheckDMRules
+=================
+*/
+void CheckDMRules (void)
+{
+	int			i;
+	gclient_t	*cl;
+
+	if (level.intermissiontime)
+		return;
+
+	if (!deathmatch->value)
+		return;
+
+//=======
+//ROGUE
+	if (gamerules && gamerules->value && DMGame.CheckDMRules)
+	{
+		if(DMGame.CheckDMRules())
+			return;
+	}
+//ROGUE
+//=======
+
+	if (timelimit->value)
+	{
+		if (level.time >= timelimit->value*60)
+		{
+			gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
+			EndDMLevel ();
+			return;
+		}
+	}
+
+	if (fraglimit->value)
+	{
+		for (i=0 ; i<maxclients->value ; i++)
+		{
+			cl = game.clients + i;
+			if (!g_edicts[i+1].inuse)
+				continue;
+
+			if (cl->resp.score >= fraglimit->value)
+			{
+				gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
+				EndDMLevel ();
+				return;
+			}
+		}
+	}
+}
+
+
+/*
+=============
+ExitLevel
+=============
+*/
+void ExitLevel (void)
+{
+	int		i;
+	edict_t	*ent;
+	char	command [256];
+
+	Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
+	gi.AddCommandString (command);
+	level.changemap = NULL;
+	level.exitintermission = 0;
+	level.intermissiontime = 0;
+	ClientEndServerFrames ();
+
+	// clear some things before going to next level
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse)
+			continue;
+		if (ent->health > ent->client->pers.max_health)
+			ent->health = ent->client->pers.max_health;
+	}
+
+}
+
+/*
+================
+G_RunFrame
+
+Advances the world by 0.1 seconds
+================
+*/
+void G_RunFrame (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.framenum++;
+	level.time = level.framenum*FRAMETIME;
+
+	// choose a client for monsters to target this frame
+	AI_SetSightClient ();
+
+	// exit intermissions
+
+	if (level.exitintermission)
+	{
+		ExitLevel ();
+		return;
+	}
+
+	//
+	// treat each object in turn
+	// even the world gets a chance to think
+	//
+	ent = &g_edicts[0];
+	for (i=0 ; i<globals.num_edicts ; i++, ent++)
+	{
+		if (!ent->inuse)
+			continue;
+
+		level.current_entity = ent;
+
+		VectorCopy (ent->s.origin, ent->s.old_origin);
+
+		// if the ground entity moved, make sure we are still on it
+		if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount))
+		{
+			ent->groundentity = NULL;
+			if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) )
+			{
+				M_CheckGround (ent);
+			}
+		}
+
+		if (i > 0 && i <= maxclients->value)
+		{
+			ClientBeginServerFrame (ent);
+			continue;
+		}
+
+		G_RunEntity (ent);
+	}
+
+	// see if it is time to end a deathmatch
+	CheckDMRules ();
+
+	// build the playerstate_t structures for all players
+	ClientEndServerFrames ();
+}
+
--- /dev/null
+++ b/rogue/g_misc.c
@@ -1,0 +1,1998 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+extern void M_WorldEffects (edict_t *ent);
+
+/*QUAKED func_group (0 0 0) ?
+Used to group brushes together just for editor convenience.
+*/
+
+//=====================================================
+
+void Use_Areaportal (edict_t *ent, edict_t *, edict_t *)
+{
+	ent->count ^= 1;		// toggle state
+//	gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count);
+	gi.SetAreaPortalState (ent->style, ent->count);
+}
+
+/*QUAKED func_areaportal (0 0 0) ?
+
+This is a non-visible object that divides the world into
+areas that are seperated when this portal is not activated.
+Usually enclosed in the middle of a door.
+*/
+void SP_func_areaportal (edict_t *ent)
+{
+	ent->use = Use_Areaportal;
+	ent->count = 0;		// always start closed;
+}
+
+//=====================================================
+
+
+/*
+=================
+Misc functions
+=================
+*/
+void VelocityForDamage (int damage, vec3_t v)
+{
+	v[0] = 100.0 * crandom();
+	v[1] = 100.0 * crandom();
+	v[2] = 200.0 + 100.0 * qrandom();
+
+	if (damage < 50)
+		VectorScale (v, 0.7, v);
+	else 
+		VectorScale (v, 1.2, v);
+}
+
+void ClipGibVelocity (edict_t *ent)
+{
+	if (ent->velocity[0] < -300)
+		ent->velocity[0] = -300;
+	else if (ent->velocity[0] > 300)
+		ent->velocity[0] = 300;
+	if (ent->velocity[1] < -300)
+		ent->velocity[1] = -300;
+	else if (ent->velocity[1] > 300)
+		ent->velocity[1] = 300;
+	if (ent->velocity[2] < 200)
+		ent->velocity[2] = 200;	// always some upwards
+	else if (ent->velocity[2] > 500)
+		ent->velocity[2] = 500;
+}
+
+
+/*
+=================
+gibs
+=================
+*/
+void gib_think (edict_t *self)
+{
+	self->s.frame++;
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->s.frame == 10)
+	{
+		self->think = G_FreeEdict;
+		self->nextthink = level.time + 8 + qrandom()*10;
+	}
+}
+
+void gib_touch (edict_t *self, edict_t *, cplane_t *plane, csurface_t *)
+{
+	vec3_t	normal_angles, right;
+
+	if (!self->groundentity)
+		return;
+
+	self->touch = NULL;
+
+	if (plane)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+		vectoangles (plane->normal, normal_angles);
+		AngleVectors (normal_angles, NULL, right, NULL);
+		vectoangles (right, self->s.angles);
+
+		if (self->s.modelindex == sm_meat_index)
+		{
+			self->s.frame++;
+			self->think = gib_think;
+			self->nextthink = level.time + FRAMETIME;
+		}
+	}
+}
+
+void gib_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowGib (edict_t *self, char *gibname, int damage, int type)
+{
+	edict_t *gib;
+	vec3_t	vd;
+	vec3_t	origin;
+	vec3_t	size;
+	float	vscale;
+
+	gib = G_Spawn();
+
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	gib->s.origin[0] = origin[0] + crandom() * size[0];
+	gib->s.origin[1] = origin[1] + crandom() * size[1];
+	gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+	gi.setmodel (gib, gibname);
+	gib->solid = SOLID_NOT;
+	gib->s.effects |= EF_GIB;
+	gib->flags |= FL_NO_KNOCKBACK;
+	gib->takedamage = DAMAGE_YES;
+	gib->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		gib->movetype = MOVETYPE_TOSS;
+		gib->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		gib->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, gib->velocity);
+	ClipGibVelocity (gib);
+	gib->avelocity[0] = qrandom()*600;
+	gib->avelocity[1] = qrandom()*600;
+	gib->avelocity[2] = qrandom()*600;
+
+	gib->think = G_FreeEdict;
+	gib->nextthink = level.time + 10 + qrandom()*10;
+
+//PGM
+	gib->s.renderfx |= RF_IR_VISIBLE;
+//PGM
+
+	gi.linkentity (gib);
+}
+
+void ThrowHead (edict_t *self, char *gibname, int damage, int type)
+{
+	vec3_t	vd;
+	float	vscale;
+
+	self->s.skinnum = 0;
+	self->s.frame = 0;
+	VectorClear (self->mins);
+	VectorClear (self->maxs);
+
+	self->s.modelindex2 = 0;
+	gi.setmodel (self, gibname);
+	self->solid = SOLID_NOT;
+	self->s.effects |= EF_GIB;
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+	self->svflags &= ~SVF_MONSTER;
+	self->takedamage = DAMAGE_YES;
+	self->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		self->movetype = MOVETYPE_TOSS;
+		self->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		self->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, self->velocity);
+	ClipGibVelocity (self);
+
+	self->avelocity[YAW] = crandom()*600;
+
+	self->think = G_FreeEdict;
+	self->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (self);
+}
+
+
+void ThrowClientHead (edict_t *self, int damage)
+{
+	vec3_t	vd;
+	char	*gibname;
+
+	if (rand()&1)
+	{
+		gibname = "models/objects/gibs/head2/tris.md2";
+		self->s.skinnum = 1;		// second skin is player
+	}
+	else
+	{
+		gibname = "models/objects/gibs/skull/tris.md2";
+		self->s.skinnum = 0;
+	}
+
+	self->s.origin[2] += 32;
+	self->s.frame = 0;
+	gi.setmodel (self, gibname);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 16);
+
+	self->takedamage = DAMAGE_NO;
+	self->solid = SOLID_NOT;
+	self->s.effects = EF_GIB;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+
+	self->movetype = MOVETYPE_BOUNCE;
+	VelocityForDamage (damage, vd);
+	VectorAdd (self->velocity, vd, self->velocity);
+
+	if (self->client)	// bodies in the queue don't have a client anymore
+	{
+		self->client->anim_priority = ANIM_DEATH;
+		self->client->anim_end = self->s.frame;
+	}
+	else
+	{
+		self->think = NULL;
+		self->nextthink = 0;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*
+=================
+debris
+=================
+*/
+void debris_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin)
+{
+	edict_t	*chunk;
+	vec3_t	v;
+
+	chunk = G_Spawn();
+	VectorCopy (origin, chunk->s.origin);
+	gi.setmodel (chunk, modelname);
+	v[0] = 100 * crandom();
+	v[1] = 100 * crandom();
+	v[2] = 100 + 100 * crandom();
+	VectorMA (self->velocity, speed, v, chunk->velocity);
+	chunk->movetype = MOVETYPE_BOUNCE;
+	chunk->solid = SOLID_NOT;
+	chunk->avelocity[0] = qrandom()*600;
+	chunk->avelocity[1] = qrandom()*600;
+	chunk->avelocity[2] = qrandom()*600;
+	chunk->think = G_FreeEdict;
+	chunk->nextthink = level.time + 5 + qrandom()*5;
+	chunk->s.frame = 0;
+	chunk->flags = 0;
+	chunk->classname = "debris";
+	chunk->takedamage = DAMAGE_YES;
+	chunk->die = debris_die;
+	gi.linkentity (chunk);
+}
+
+
+void BecomeExplosion1 (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+void BecomeExplosion2 (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION2);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
+Target: next path corner
+Pathtarget: gets used when an entity that has
+	this path_corner targeted touches it
+*/
+
+void path_corner_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t		v;
+	edict_t		*next;
+
+	if (other->movetarget != self)
+		return;
+	
+	if (other->enemy)
+		return;
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		G_UseTargets (self, other);
+		self->target = savetarget;
+	}
+
+	if (self->target)
+		next = G_PickTarget(self->target);
+	else
+		next = NULL;
+
+	if ((next) && (next->spawnflags & 1))
+	{
+		VectorCopy (next->s.origin, v);
+		v[2] += next->mins[2];
+		v[2] -= other->mins[2];
+		VectorCopy (v, other->s.origin);
+		next = G_PickTarget(next->target);
+		other->s.event = EV_OTHER_TELEPORT;
+	}
+
+	other->goalentity = other->movetarget = next;
+
+	if (self->wait)
+	{
+		other->monsterinfo.pausetime = level.time + self->wait;
+		other->monsterinfo.stand (other);
+		return;
+	}
+
+	if (!other->movetarget)
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.stand (other);
+	}
+	else
+	{
+		VectorSubtract (other->goalentity->s.origin, other->s.origin, v);
+		other->ideal_yaw = vectoyaw (v);
+	}
+}
+
+void SP_path_corner (edict_t *self)
+{
+	if (!self->targetname)
+	{
+		gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = path_corner_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags |= SVF_NOCLIENT;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
+Makes this the target of a monster and it will head here
+when first activated before going after the activator.  If
+hold is selected, it will stay here.
+*/
+void point_combat_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t	*activator;
+
+	if (other->movetarget != self)
+		return;
+
+	if (self->target)
+	{
+		other->target = self->target;
+		other->goalentity = other->movetarget = G_PickTarget(other->target);
+		if (!other->goalentity)
+		{
+			gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
+			other->movetarget = self;
+		}
+		self->target = NULL;
+	}
+	else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY)))
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.aiflags |= AI_STAND_GROUND;
+		other->monsterinfo.stand (other);
+	}
+
+	if (other->movetarget == self)
+	{
+		other->target = NULL;
+		other->movetarget = NULL;
+		other->goalentity = other->enemy;
+		other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
+	}
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		if (other->enemy && other->enemy->client)
+			activator = other->enemy;
+		else if (other->oldenemy && other->oldenemy->client)
+			activator = other->oldenemy;
+		else if (other->activator && other->activator->client)
+			activator = other->activator;
+		else
+			activator = other;
+		G_UseTargets (self, activator);
+		self->target = savetarget;
+	}
+}
+
+void SP_point_combat (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	self->solid = SOLID_TRIGGER;
+	self->touch = point_combat_touch;
+	VectorSet (self->mins, -8, -8, -16);
+	VectorSet (self->maxs, 8, 8, 16);
+	self->svflags = SVF_NOCLIENT;
+	gi.linkentity (self);
+};
+
+
+/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
+Just for the debugging level.  Don't use
+*/
+void TH_viewthing(edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 7;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_viewthing(edict_t *ent)
+{
+	gi.dprintf ("viewthing spawned\n");
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.renderfx = RF_FRAMELERP;
+	VectorSet (ent->mins, -16, -16, -24);
+	VectorSet (ent->maxs, 16, 16, 32);
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	gi.linkentity (ent);
+	ent->nextthink = level.time + 0.5;
+	ent->think = TH_viewthing;
+	return;
+}
+
+
+/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for spotlights, etc.
+*/
+void SP_info_null (edict_t *self)
+{
+	G_FreeEdict (self);
+};
+
+
+/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for lightning.
+*/
+void SP_info_notnull (edict_t *self)
+{
+	VectorCopy (self->s.origin, self->absmin);
+	VectorCopy (self->s.origin, self->absmax);
+};
+
+
+/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
+Non-displayed light.
+Default light value is 300.
+Default style is 0.
+If targeted, will toggle between on and off.
+Default _cone value is 10 (used to set size of light for spotlights)
+*/
+
+#define START_OFF	1
+
+static void light_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & START_OFF)
+	{
+		gi.configstring (CS_LIGHTS+self->style, "m");
+		self->spawnflags &= ~START_OFF;
+	}
+	else
+	{
+		gi.configstring (CS_LIGHTS+self->style, "a");
+		self->spawnflags |= START_OFF;
+	}
+}
+
+void SP_light (edict_t *self)
+{
+	// no targeted lights in deathmatch, because they cause global messages
+	if (!self->targetname || deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->style >= 32)
+	{
+		self->use = light_use;
+		if (self->spawnflags & START_OFF)
+			gi.configstring (CS_LIGHTS+self->style, "a");
+		else
+			gi.configstring (CS_LIGHTS+self->style, "m");
+	}
+}
+
+
+/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
+This is just a solid wall if not inhibited
+
+TRIGGER_SPAWN	the wall will not be present until triggered
+				it will then blink in to existance; it will
+				kill anything that was in it's way
+
+TOGGLE			only valid for TRIGGER_SPAWN walls
+				this allows the wall to be turned on and off
+
+START_ON		only valid for TRIGGER_SPAWN walls
+				the wall will initially be present
+*/
+
+void func_wall_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+	{
+		self->solid = SOLID_BSP;
+		self->svflags &= ~SVF_NOCLIENT;
+		KillBox (self);
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+void SP_func_wall (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 8)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 16)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	// just a wall
+	if ((self->spawnflags & 7) == 0)
+	{
+		self->solid = SOLID_BSP;
+		gi.linkentity (self);
+		return;
+	}
+
+	// it must be TRIGGER_SPAWN
+	if (!(self->spawnflags & 1))
+	{
+//		gi.dprintf("func_wall missing TRIGGER_SPAWN\n");
+		self->spawnflags |= 1;
+	}
+
+	// yell if the spawnflags are odd
+	if (self->spawnflags & 4)
+	{
+		if (!(self->spawnflags & 2))
+		{
+			gi.dprintf("func_wall START_ON without TOGGLE\n");
+			self->spawnflags |= 2;
+		}
+	}
+
+	self->use = func_wall_use;
+	if (self->spawnflags & 4)
+	{
+		self->solid = SOLID_BSP;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
+This is solid bmodel that will fall if it's support it removed.
+*/
+
+void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *)
+{
+	// only squash thing we fall on top of
+	if (!plane)
+		return;
+	if (plane->normal[2] < 1.0)
+		return;
+	if (other->takedamage == DAMAGE_NO)
+		return;
+	T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void func_object_release (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->touch = func_object_touch;
+}
+
+void func_object_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	func_object_release (self);
+}
+
+void SP_func_object (edict_t *self)
+{
+	gi.setmodel (self, self->model);
+
+	self->mins[0] += 1;
+	self->mins[1] += 1;
+	self->mins[2] += 1;
+	self->maxs[0] -= 1;
+	self->maxs[1] -= 1;
+	self->maxs[2] -= 1;
+
+	if (!self->dmg)
+		self->dmg = 100;
+
+	if (self->spawnflags == 0)
+	{
+		self->solid = SOLID_BSP;
+		self->movetype = MOVETYPE_PUSH;
+		self->think = func_object_release;
+		self->nextthink = level.time + 2 * FRAMETIME;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->movetype = MOVETYPE_PUSH;
+		self->use = func_object_use;
+		self->svflags |= SVF_NOCLIENT;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	self->clipmask = MASK_MONSTERSOLID;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE
+Any brush that you want to explode or break apart.  If you want an
+ex0plosion, set dmg and it will do a radius explosion of that amount
+at the center of the bursh.
+
+If targeted it will not be shootable.
+
+INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must
+target the entity you want to trigger it. This is the only entity approved to activate it.
+
+health defaults to 100.
+
+mass defaults to 75.  This determines how much debris is emitted when
+it explodes.  You get one large chunk per 100 of mass (up to 8) and
+one small chunk per 25 of mass (up to 16).  So 800 gives the most.
+*/
+void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int, vec3_t)
+{
+	vec3_t	origin;
+	vec3_t	chunkorigin;
+	vec3_t	size;
+	int		count;
+	int		mass;
+	edict_t	*master;
+	qboolean	done = false;
+
+	// bmodel origins are (0 0 0), we need to adjust that here
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	VectorCopy (origin, self->s.origin);
+
+	self->takedamage = DAMAGE_NO;
+
+	if (self->dmg)
+		T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity);
+	VectorNormalize (self->velocity);
+	VectorScale (self->velocity, 150, self->velocity);
+
+	// start chunks towards the center
+	VectorScale (size, 0.5, size);
+
+	mass = self->mass;
+	if (!mass)
+		mass = 75;
+
+	// big chunks
+	if (mass >= 100)
+	{
+		count = mass / 100;
+		if (count > 8)
+			count = 8;
+		while(count--)
+		{
+			chunkorigin[0] = origin[0] + crandom() * size[0];
+			chunkorigin[1] = origin[1] + crandom() * size[1];
+			chunkorigin[2] = origin[2] + crandom() * size[2];
+			ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin);
+		}
+	}
+
+	// small chunks
+	count = mass / 25;
+	if (count > 16)
+		count = 16;
+	while(count--)
+	{
+		chunkorigin[0] = origin[0] + crandom() * size[0];
+		chunkorigin[1] = origin[1] + crandom() * size[1];
+		chunkorigin[2] = origin[2] + crandom() * size[2];
+		ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin);
+	}
+
+	// PMM - if we're part of a train, clean ourselves out of it
+	if (self->flags & FL_TEAMSLAVE)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Removing func_explosive from train!\n");
+
+		if (self->teammaster)
+		{
+			master = self->teammaster;
+			if(master && master->inuse)		// because mappers (other than jim (usually)) are stupid....
+			{
+				while (!done)
+				{
+					if (master->teamchain == self)
+					{
+						master->teamchain = self->teamchain;
+						done = true;
+					}
+					master = master->teamchain;
+					if (!master)
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("Couldn't find myself in master's chain, ignoring!\n");
+					}
+				}
+			}
+		}
+		else
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("No master to free myself from, ignoring!\n");
+		}
+	}
+
+	G_UseTargets (self, attacker);
+
+	if (self->dmg)
+		BecomeExplosion1 (self);
+	else
+		G_FreeEdict (self);
+}
+
+void func_explosive_use(edict_t *self, edict_t *other, edict_t *)
+{
+	func_explosive_explode (self, self, other, self->health, vec3_origin);
+}
+
+//PGM
+void func_explosive_activate(edict_t *self, edict_t *other, edict_t *activator)
+{
+	int approved;
+
+	approved = 0;
+	// PMM - looked like target and targetname were flipped here
+	if (other != NULL && other->target)
+	{
+		if(!strcmp(other->target, self->targetname))
+			approved = 1;
+	}
+	if (!approved && activator!=NULL && activator->target)
+	{
+		if(!strcmp(activator->target, self->targetname))
+			approved = 1;
+	}
+
+	if (!approved)
+	{
+//		gi.dprintf("func_explosive_activate: incorrect activator\n");
+		return;
+	}
+
+	// PMM - according to mappers, they don't need separate cases for blowupable and triggerable
+//	if (self->target)
+//	{
+		self->use = func_explosive_use;
+//	}
+//	else
+//	{
+		if (!self->health)
+			self->health = 100;
+		self->die = func_explosive_explode;
+		self->takedamage = DAMAGE_YES;
+//	}
+}
+//PGM
+
+void func_explosive_spawn (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	gi.linkentity (self);
+}
+
+void SP_func_explosive (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 1)
+	{
+		self->svflags |= SVF_NOCLIENT;
+		self->solid = SOLID_NOT;
+		self->use = func_explosive_spawn;
+	}
+//PGM
+	else if(self->spawnflags & 8)
+	{
+		self->solid = SOLID_BSP;
+		if(self->targetname)
+			self->use = func_explosive_activate;
+	}
+//PGM
+	else
+	{
+		self->solid = SOLID_BSP;
+		if (self->targetname)
+			self->use = func_explosive_use;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+//PGM
+	if ((self->use != func_explosive_use) && (self->use != func_explosive_activate))
+//PGM
+	{
+		if (!self->health)
+			self->health = 100;
+		self->die = func_explosive_explode;
+		self->takedamage = DAMAGE_YES;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
+Large exploding box.  You can override its mass (100),
+health (80), and dmg (150).
+*/
+
+void barrel_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	float	ratio;
+	vec3_t	v;
+
+	if ((!other->groundentity) || (other->groundentity == self))
+		return;
+
+	ratio = (float)other->mass / (float)self->mass;
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME);
+}
+
+void barrel_explode (edict_t *self)
+{
+	vec3_t	org;
+	float	spd;
+	vec3_t	save;
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL);
+
+	VectorCopy (self->s.origin, save);
+	VectorMA (self->absmin, 0.5, self->size, self->s.origin);
+
+	// a few big chunks
+	spd = 1.5 * (float)self->dmg / 200.0;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+
+	// bottom corners
+	spd = 1.75 * (float)self->dmg / 200.0;
+	VectorCopy (self->absmin, org);
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+
+	// a bunch of little chunks
+	spd = 2 * self->dmg / 200;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+
+	VectorCopy (save, self->s.origin);
+	if (self->groundentity)
+		BecomeExplosion2 (self);
+	else
+		BecomeExplosion1 (self);
+}
+
+void barrel_delay (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	self->nextthink = level.time + 2 * FRAMETIME;
+	self->think = barrel_explode;
+	self->activator = attacker;
+}
+
+//=========
+//PGM  - change so barrels will think and hence, blow up
+void barrel_think (edict_t *self)
+{
+	// the think needs to be first since later stuff may override.
+	self->think = barrel_think;
+	self->nextthink = level.time + FRAMETIME;
+
+	M_CatagorizePosition (self);
+	self->flags |= FL_IMMUNE_SLIME;
+	self->air_finished = level.time + 100;
+	M_WorldEffects (self);
+}
+
+void barrel_start (edict_t *self)
+{
+	M_droptofloor(self);
+	self->think = barrel_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+//PGM
+//=========
+
+void SP_misc_explobox (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+	gi.modelindex ("models/objects/debris3/tris.md2");
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+
+	self->model = "models/objects/barrels/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 40);
+
+	if (!self->mass)
+		self->mass = 400;
+	if (!self->health)
+		self->health = 10;
+	if (!self->dmg)
+		self->dmg = 150;
+
+	self->die = barrel_delay;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.aiflags = AI_NOSTEP;
+
+	self->touch = barrel_touch;
+
+//PGM - change so barrels will think and hence, blow up
+	self->think = barrel_start;
+	self->nextthink = level.time + 2 * FRAMETIME;
+//PGM
+
+	gi.linkentity (self);
+}
+
+
+//
+// miscellaneous specialty items
+//
+
+/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
+*/
+
+void misc_blackhole_use (edict_t *ent, edict_t *, edict_t *)
+{
+	/*
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	*/
+	G_FreeEdict (ent);
+}
+
+void misc_blackhole_think (edict_t *self)
+{
+	if (++self->s.frame < 19)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 0;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_blackhole (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 8);
+	ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2");
+	ent->s.renderfx = RF_TRANSLUCENT;
+	ent->use = misc_blackhole_use;
+	ent->think = misc_blackhole_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
+*/
+
+void misc_eastertank_think (edict_t *self)
+{
+	if (++self->s.frame < 293)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 254;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_eastertank (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, -16);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
+	ent->s.frame = 254;
+	ent->think = misc_eastertank_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick_think (edict_t *self)
+{
+	if (++self->s.frame < 247)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 208;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 208;
+	ent->think = misc_easterchick_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick2_think (edict_t *self)
+{
+	if (++self->s.frame < 287)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 248;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 248;
+	ent->think = misc_easterchick2_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
+Not really a monster, this is the Tank Commander's decapitated body.
+There should be a item_commander_head that has this as it's target.
+*/
+
+void commander_body_think (edict_t *self)
+{
+	if (++self->s.frame < 24)
+		self->nextthink = level.time + FRAMETIME;
+	else
+		self->nextthink = 0;
+
+	if (self->s.frame == 22)
+		gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->think = commander_body_think;
+	self->nextthink = level.time + FRAMETIME;
+	gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_drop (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->s.origin[2] += 2;
+}
+
+void SP_monster_commander_body (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_BBOX;
+	self->model = "models/monsters/commandr/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -32, -32, 0);
+	VectorSet (self->maxs, 32, 32, 48);
+	self->use = commander_body_use;
+	self->takedamage = DAMAGE_YES;
+	self->flags = FL_GODMODE;
+	self->s.renderfx |= RF_FRAMELERP;
+	gi.linkentity (self);
+
+	gi.soundindex ("tank/thud.wav");
+	gi.soundindex ("tank/pain.wav");
+
+	self->think = commander_body_drop;
+	self->nextthink = level.time + 5 * FRAMETIME;
+}
+
+
+/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
+The origin is the bottom of the banner.
+The banner is 128 tall.
+*/
+void misc_banner_think (edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 16;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_banner (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	ent->s.frame = rand() % 16;
+	gi.linkentity (ent);
+
+	ent->think = misc_banner_think;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
+This is the dead player model. Comes in 6 exciting different poses!
+*/
+void misc_deadsoldier_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+//	if (self->health > -80)
+	if (self->health > -30)
+		return;
+
+	gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+	for (n= 0; n < 4; n++)
+		ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+	ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+}
+
+void SP_misc_deadsoldier (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2");
+
+	// Defaults to frame 0
+	if (ent->spawnflags & 2)
+		ent->s.frame = 1;
+	else if (ent->spawnflags & 4)
+		ent->s.frame = 2;
+	else if (ent->spawnflags & 8)
+		ent->s.frame = 3;
+	else if (ent->spawnflags & 16)
+		ent->s.frame = 4;
+	else if (ent->spawnflags & 32)
+		ent->s.frame = 5;
+	else
+		ent->s.frame = 0;
+
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 16);
+	ent->deadflag = DEAD_DEAD;
+	ent->takedamage = DAMAGE_YES;
+	ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER;
+	ent->die = misc_deadsoldier_die;
+	ent->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
+This is the Viper for the flyby bombing.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast the Viper should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_viper_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_viper (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_viper_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) 
+This is a large stationary viper as seen in Paul's intro
+*/
+void SP_misc_bigviper (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -176, -120, -24);
+	VectorSet (ent->maxs, 176, 120, 72);
+	ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
+"dmg"	how much boom should the bomb make?
+*/
+void misc_viper_bomb_touch (edict_t *self, edict_t *, cplane_t *, csurface_t *)
+{
+	G_UseTargets (self, self->activator);
+
+	self->s.origin[2] = self->absmin[2] + 1;
+	T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB);
+	BecomeExplosion2 (self);
+}
+
+void misc_viper_bomb_prethink (edict_t *self)
+{
+	vec3_t	v;
+	float	diff;
+
+	self->groundentity = NULL;
+
+	diff = self->timestamp - level.time;
+	if (diff < -1.0)
+		diff = -1.0;
+
+	VectorScale (self->moveinfo.dir, 1.0 + diff, v);
+	v[2] = diff;
+
+	diff = self->s.angles[2];
+	vectoangles (v, self->s.angles);
+	self->s.angles[2] = diff + 10;
+}
+
+void misc_viper_bomb_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*viper;
+
+	self->solid = SOLID_BBOX;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->s.effects |= EF_ROCKET;
+	self->use = NULL;
+	self->movetype = MOVETYPE_TOSS;
+	self->prethink = misc_viper_bomb_prethink;
+	self->touch = misc_viper_bomb_touch;
+	self->activator = activator;
+
+	viper = G_Find (NULL, FOFS(classname), "misc_viper");
+	VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
+
+	self->timestamp = level.time;
+	VectorCopy (viper->moveinfo.dir, self->moveinfo.dir);
+}
+
+void SP_misc_viper_bomb (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+
+	self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
+
+	if (!self->dmg)
+		self->dmg = 1000;
+
+	self->use = misc_viper_bomb_use;
+	self->svflags |= SVF_NOCLIENT;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
+This is a Storgg ship for the flybys.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast it should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_strogg_ship_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_strogg_ship (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_strogg_ship_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+*/
+void misc_satellite_dish_think (edict_t *self)
+{
+	self->s.frame++;
+	if (self->s.frame < 38)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void misc_satellite_dish_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->s.frame = 0;
+	self->think = misc_satellite_dish_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_satellite_dish (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 128);
+	ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2");
+	ent->use = misc_satellite_dish_use;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine1 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_arm (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/arm/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_leg (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/leg/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_head (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/head/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+//=====================================================
+
+/*QUAKED target_character (0 0 1) ?
+used with target_string (must be on same "team")
+"count" is position in the string (starts at 1)
+*/
+
+void SP_target_character (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	self->s.frame = 12;
+	gi.linkentity (self);
+	return;
+}
+
+
+/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+*/
+
+void target_string_use (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t *e;
+	int		n, l;
+	char	c;
+
+	l = strlen(self->message);
+	for (e = self->teammaster; e; e = e->teamchain)
+	{
+		if (!e->count)
+			continue;
+		n = e->count - 1;
+		if (n > l)
+		{
+			e->s.frame = 12;
+			continue;
+		}
+
+		c = self->message[n];
+		if (c >= '0' && c <= '9')
+			e->s.frame = c - '0';
+		else if (c == '-')
+			e->s.frame = 10;
+		else if (c == ':')
+			e->s.frame = 11;
+		else
+			e->s.frame = 12;
+	}
+}
+
+void SP_target_string (edict_t *self)
+{
+	if (!self->message)
+		self->message = "";
+	self->use = target_string_use;
+}
+
+
+/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
+target a target_string with this
+
+The default is to be a time of day clock
+
+TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+If START_OFF, this entity must be used before it starts
+
+"style"		0 "xx"
+			1 "xx:xx"
+			2 "xx:xx:xx"
+*/
+
+#define	CLOCK_MESSAGE_SIZE	16
+
+// don't let field width of any clock messages change, or it
+// could cause an overwrite after a game load
+
+static void func_clock_reset (edict_t *self)
+{
+	self->activator = NULL;
+	if (self->spawnflags & 1)
+	{
+		self->health = 0;
+		self->wait = self->count;
+	}
+	else if (self->spawnflags & 2)
+	{
+		self->health = self->count;
+		self->wait = 0;
+	}
+}
+
+static void func_clock_format_countdown (edict_t *self)
+{
+	if (self->style == 0)
+	{
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
+		return;
+	}
+
+	if (self->style == 1)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		return;
+	}
+
+	if (self->style == 2)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+		return;
+	}
+}
+
+void func_clock_think (edict_t *self)
+{
+	Tm *t;
+
+	if (!self->enemy)
+	{
+		self->enemy = G_Find (NULL, FOFS(targetname), self->target);
+		if (!self->enemy)
+			return;
+	}
+
+	if (self->spawnflags & 1)
+	{
+		func_clock_format_countdown (self);
+		self->health++;
+	}
+	else if (self->spawnflags & 2)
+	{
+		func_clock_format_countdown (self);
+		self->health--;
+	}
+	else
+	{
+		t = localtime(time(nil));
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2d:%2d:%2d", t->hour, t->min, t->sec);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+	}
+
+	self->enemy->message = self->message;
+	self->enemy->use (self->enemy, self, self);
+
+	if (((self->spawnflags & 1) && (self->health > self->wait)) ||
+		((self->spawnflags & 2) && (self->health < self->wait)))
+	{
+		if (self->pathtarget)
+		{
+			char *savetarget;
+			char *savemessage;
+
+			savetarget = self->target;
+			savemessage = self->message;
+			self->target = self->pathtarget;
+			self->message = NULL;
+			G_UseTargets (self, self->activator);
+			self->target = savetarget;
+			self->message = savemessage;
+		}
+
+		if (!(self->spawnflags & 8))
+			return;
+
+		func_clock_reset (self);
+
+		if (self->spawnflags & 4)
+			return;
+	}
+
+	self->nextthink = level.time + 1;
+}
+
+void func_clock_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (!(self->spawnflags & 8))
+		self->use = NULL;
+	if (self->activator)
+		return;
+	self->activator = activator;
+	self->think (self);
+}
+
+void SP_func_clock (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 2) && (!self->count))
+	{
+		gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 1) && (!self->count))
+		self->count = 60*60;;
+
+	func_clock_reset (self);
+
+	self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL);
+
+	self->think = func_clock_think;
+
+	if (self->spawnflags & 4)
+		self->use = func_clock_use;
+	else
+		self->nextthink = level.time + 1;
+}
+
+//=================================================================================
+
+void teleporter_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t		*dest;
+	int			i;
+
+	if (!other->client)
+		return;
+	dest = G_Find (NULL, FOFS(targetname), self->target);
+	if (!dest)
+	{
+		gi.dprintf ("Couldn't find destination\n");
+		return;
+	}
+
+	// unlink to make sure it can't possibly interfere with KillBox
+	gi.unlinkentity (other);
+
+	VectorCopy (dest->s.origin, other->s.origin);
+	VectorCopy (dest->s.origin, other->s.old_origin);
+	other->s.origin[2] += 10;
+
+	// clear the velocity and hold them in place briefly
+	VectorClear (other->velocity);
+	other->client->ps.pmove.pm_time = 160>>3;		// hold time
+	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+	// draw the teleport splash at source and on the player
+	self->owner->s.event = EV_PLAYER_TELEPORT;
+	other->s.event = EV_PLAYER_TELEPORT;
+
+	// set angles
+	for (i=0 ; i<3 ; i++)
+		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+
+	VectorClear (other->s.angles);
+	VectorClear (other->client->ps.viewangles);
+	VectorClear (other->client->v_angle);
+
+	// kill anything at the destination
+	KillBox (other);
+
+	gi.linkentity (other);
+}
+
+/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
+Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
+*/
+void SP_misc_teleporter (edict_t *ent)
+{
+	edict_t		*trig;
+
+	if (!ent->target)
+	{
+		gi.dprintf ("teleporter without a target.\n");
+		G_FreeEdict (ent);
+		return;
+	}
+
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 1;
+	ent->s.effects = EF_TELEPORTER;
+	ent->s.sound = gi.soundindex ("world/amb10.wav");
+	ent->solid = SOLID_BBOX;
+
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+
+	trig = G_Spawn ();
+	trig->touch = teleporter_touch;
+	trig->solid = SOLID_TRIGGER;
+	trig->target = ent->target;
+	trig->owner = ent;
+	VectorCopy (ent->s.origin, trig->s.origin);
+	VectorSet (trig->mins, -8, -8, 8);
+	VectorSet (trig->maxs, 8, 8, 24);
+	gi.linkentity (trig);
+	
+}
+
+/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+Point teleporters at these.
+*/
+void SP_misc_teleporter_dest (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 0;
+	ent->solid = SOLID_BBOX;
+//	ent->s.effects |= EF_FLIES;
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+}
+
+//======================
+//ROGUE
+void misc_nuke_core_use (edict_t *self, edict_t *, edict_t *)
+{
+	if(self->svflags & SVF_NOCLIENT)
+		self->svflags &= ~SVF_NOCLIENT;
+	else
+		self->svflags |= SVF_NOCLIENT;
+}
+
+/*QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16)
+toggles visible/not visible. starts visible.
+*/
+void SP_misc_nuke_core (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/core/tris.md2");
+	gi.linkentity (ent);
+
+	ent->use = misc_nuke_core_use;
+}
+//ROGUE
+//======================
+
--- /dev/null
+++ b/rogue/g_monster.c
@@ -1,0 +1,983 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+//
+// monster weapons
+//
+
+//FIXME mosnters should call these with a totally accurate direction
+// and we can mess it up based on skill.  Spread should be for normal
+// and we can tighten or loosen based on skill.  We could muck with
+// the damages too, but I'm not sure that's such a good idea.
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
+{
+	fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
+{
+	fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
+{
+	fire_blaster (self, start, dir, damage, speed, effect, false);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+//ROGUE
+void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
+{
+	fire_blaster2 (self, start, dir, damage, speed, effect, false);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+// FIXME -- add muzzle flash
+void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype)
+{
+	fire_tracker (self, start, dir, damage, speed, enemy);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype)
+{
+	fire_heat (self, start, dir, offset, damage, kick, true);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+//ROGUE
+
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
+{
+	fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
+{
+	fire_rocket (self, start, dir, damage, speed, damage+20, damage);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
+{
+	// PMM
+	if (!(gi.pointcontents (start) & MASK_SOLID))
+		fire_rail (self, start, aimdir, damage, kick);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int, float damage_radius, int flashtype)
+{
+	fire_bfg (self, start, aimdir, damage, speed, damage_radius);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+
+
+//
+// Monster utility functions
+//
+
+void M_FliesOff (edict_t *self)
+{
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+}
+
+void M_FliesOn (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+	self->s.effects |= EF_FLIES;
+	self->s.sound = gi.soundindex ("infantry/inflies1.wav");
+	self->think = M_FliesOff;
+	self->nextthink = level.time + 60;
+}
+
+void M_FlyCheck (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+
+	if (qrandom() > 0.5)
+		return;
+
+	self->think = M_FliesOn;
+	self->nextthink = level.time + 5 + 10 * qrandom();
+}
+
+void AttackFinished (edict_t *self, float time)
+{
+	self->monsterinfo.attack_finished = level.time + time;
+}
+
+
+void M_CheckGround (edict_t *ent)
+{
+	vec3_t		point;
+	trace_t		trace;
+
+	if (ent->flags & (FL_SWIM|FL_FLY))
+		return;
+
+#ifdef ROGUE_GRAVITY
+	if ((ent->velocity[2] * ent->gravityVector[2]) < -100)		// PGM
+#else
+	if (ent->velocity[2] > 100)
+#endif
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+
+// if the hull point one-quarter unit down is solid the entity is on ground
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+#ifdef ROGUE_GRAVITY
+	point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]);	//PGM
+#else
+	point[2] = ent->s.origin[2] - 0.25;
+#endif
+
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
+
+	// check steepness
+#ifdef ROGUE_GRAVITY
+//PGM
+	if ( ent->gravityVector[2] < 0)		// normal gravity
+	{
+		if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
+		{
+			ent->groundentity = NULL;
+			return;
+		}
+	}
+	else								// inverted gravity
+	{
+		if ( trace.plane.normal[2] > -0.7 && !trace.startsolid)
+		{
+			ent->groundentity = NULL;
+			return;
+		}
+	}
+//PGM
+#else
+	if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+#endif
+
+//	ent->groundentity = trace.ent;
+//	ent->groundentity_linkcount = trace.ent->linkcount;
+//	if (!trace.startsolid && !trace.allsolid)
+//		VectorCopy (trace.endpos, ent->s.origin);
+	if (!trace.startsolid && !trace.allsolid)
+	{
+		VectorCopy (trace.endpos, ent->s.origin);
+		ent->groundentity = trace.ent;
+		ent->groundentity_linkcount = trace.ent->linkcount;
+		ent->velocity[2] = 0;
+	}
+}
+
+
+void M_CatagorizePosition (edict_t *ent)
+{
+	vec3_t		point;
+	int			cont;
+
+//
+// get waterlevel
+//
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+	point[2] = ent->s.origin[2] + ent->mins[2] + 1;	
+	cont = gi.pointcontents (point);
+
+	if (!(cont & MASK_WATER))
+	{
+		ent->waterlevel = 0;
+		ent->watertype = 0;
+		return;
+	}
+
+	ent->watertype = cont;
+	ent->waterlevel = 1;
+	point[2] += 26;
+	cont = gi.pointcontents (point);
+	if (!(cont & MASK_WATER))
+		return;
+
+	ent->waterlevel = 2;
+	point[2] += 22;
+	cont = gi.pointcontents (point);
+	if (cont & MASK_WATER)
+		ent->waterlevel = 3;
+}
+
+
+void M_WorldEffects (edict_t *ent)
+{
+	int		dmg;
+
+	if (ent->health > 0)
+	{
+		if (!(ent->flags & FL_SWIM))
+		{
+			if (ent->waterlevel < 3)
+			{
+				ent->air_finished = level.time + 12;
+			}
+			else if (ent->air_finished < level.time)
+			{	// drown!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+		else
+		{
+			if (ent->waterlevel > 0)
+			{
+				ent->air_finished = level.time + 9;
+			}
+			else if (ent->air_finished < level.time)
+			{	// suffocate!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+	}
+	
+	if (ent->waterlevel == 0)
+	{
+		if (ent->flags & FL_INWATER)
+		{	
+			gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+			ent->flags &= ~FL_INWATER;
+		}
+		return;
+	}
+
+	if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 0.2;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
+		}
+	}
+	if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 1;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
+		}
+	}
+	
+	if ( !(ent->flags & FL_INWATER) )
+	{	
+		if (!(ent->svflags & SVF_DEADMONSTER))
+		{
+			if (ent->watertype & CONTENTS_LAVA)
+				if (qrandom() <= 0.5)
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_SLIME)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_WATER)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		}
+
+		ent->flags |= FL_INWATER;
+		ent->damage_debounce_time = 0;
+	}
+}
+
+
+void M_droptofloor (edict_t *ent)
+{
+	vec3_t		end;
+	trace_t		trace;
+
+#ifdef ROGUE_GRAVITY
+//PGM
+	if(ent->gravityVector[2] < 0)
+	{
+		ent->s.origin[2] += 1;
+		VectorCopy (ent->s.origin, end);
+		end[2] -= 256;
+	}
+	else
+	{
+		ent->s.origin[2] -= 1;
+		VectorCopy (ent->s.origin, end);
+		end[2] += 256;
+	}
+//PGM
+#else
+	ent->s.origin[2] += 1;
+	VectorCopy (ent->s.origin, end);
+	end[2] -= 256;
+#endif
+
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1 || trace.allsolid)
+		return;
+
+	VectorCopy (trace.endpos, ent->s.origin);
+
+	gi.linkentity (ent);
+	M_CheckGround (ent);
+	M_CatagorizePosition (ent);
+}
+
+
+void M_SetEffects (edict_t *ent)
+{
+	int remaining;
+
+	ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN|EF_DOUBLE|EF_QUAD|EF_PENT);
+	ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE|RF_SHELL_DOUBLE);
+
+	if (ent->monsterinfo.aiflags & AI_RESURRECTING)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= RF_SHELL_RED;
+	}
+
+	if (ent->health <= 0)
+		return;
+
+	if (ent->powerarmor_time > level.time)
+	{
+		if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+	// PMM - new monster powerups
+	if (ent->monsterinfo.quad_framenum > level.framenum)
+	{
+		remaining = ent->monsterinfo.quad_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_QUAD;
+	}
+	else
+		ent->s.effects &= ~EF_QUAD;
+
+	if (ent->monsterinfo.double_framenum > level.framenum)
+	{
+		remaining = ent->monsterinfo.double_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_DOUBLE;
+	}
+	else
+		ent->s.effects &= ~EF_DOUBLE;
+
+	if (ent->monsterinfo.invincible_framenum > level.framenum)
+	{
+		remaining = ent->monsterinfo.invincible_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_PENT;
+	}
+	else
+		ent->s.effects &= ~EF_PENT;
+
+	// PMM
+	// PMM - testing
+//	ent->s.effects |= EF_COLOR_SHELL;
+//	ent->s.renderfx |= RF_SHELL_HALF_DAM;
+/*
+	if (fmod (level.time, 4.0) > 2.0)
+	{
+		gi.dprintf ("invulnerable ");
+		ent->s.renderfx |= RF_SHELL_RED;
+	}
+	else
+		ent->s.renderfx &= ~RF_SHELL_RED;
+
+	if (fmod (level.time, 8.0) > 4.0)
+	{
+		gi.dprintf ("shield ");
+		ent->s.renderfx |= RF_SHELL_GREEN;
+	}
+	else
+		ent->s.renderfx &= ~RF_SHELL_GREEN;
+
+	if (fmod (level.time, 16.0) > 8.0)
+	{
+		gi.dprintf ("quad ");
+		ent->s.renderfx |= RF_SHELL_BLUE;\
+	}
+	else
+		ent->s.renderfx &= ~RF_SHELL_BLUE;
+
+	if (fmod (level.time, 32.0) > 16.0)
+	{
+		gi.dprintf ("double ");
+		ent->s.renderfx |= RF_SHELL_DOUBLE;
+	}
+	else
+		ent->s.renderfx &= ~RF_SHELL_DOUBLE;
+
+	if (fmod (level.time, 64.0) > 32.0)
+	{
+		gi.dprintf ("half ");
+		ent->s.renderfx |= RF_SHELL_HALF_DAM;
+	}
+	else
+		ent->s.renderfx &= ~RF_SHELL_HALF_DAM;
+
+	gi.dprintf ("\n");
+*/
+}
+
+
+void M_MoveFrame (edict_t *self)
+{
+	mmove_t	*move;
+	int		index;
+
+	move = self->monsterinfo.currentmove;
+	self->nextthink = level.time + FRAMETIME;
+
+	if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
+	{
+		self->s.frame = self->monsterinfo.nextframe;
+		self->monsterinfo.nextframe = 0;
+	}
+	else
+	{
+		if (self->s.frame == move->lastframe)
+		{
+			if (move->endfunc)
+			{
+				move->endfunc (self);
+
+				// regrab move, endfunc is very likely to change it
+				move = self->monsterinfo.currentmove;
+
+				// check for death
+				if (self->svflags & SVF_DEADMONSTER)
+					return;
+			}
+		}
+
+		if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
+		{
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+			self->s.frame = move->firstframe;
+		}
+		else
+		{
+			if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			{
+				self->s.frame++;
+				if (self->s.frame > move->lastframe)
+					self->s.frame = move->firstframe;
+			}
+		}
+	}
+
+	index = self->s.frame - move->firstframe;
+
+	if (move->frame[index].aifunc)
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
+		else
+			move->frame[index].aifunc (self, 0);
+
+	if (move->frame[index].thinkfunc)
+		move->frame[index].thinkfunc (self);
+}
+
+
+void monster_think (edict_t *self)
+{
+	M_MoveFrame (self);
+	if (self->linkcount != self->monsterinfo.linkcount)
+	{
+		self->monsterinfo.linkcount = self->linkcount;
+		M_CheckGround (self);
+	}
+	M_CatagorizePosition (self);
+	M_WorldEffects (self);
+	M_SetEffects (self);
+}
+
+
+/*
+================
+monster_use
+
+Using a monster makes it angry at the current activator
+================
+*/
+void monster_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->enemy)
+		return;
+	if (self->health <= 0)
+		return;
+	if (activator->flags & FL_NOTARGET)
+		return;
+	if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
+		return;
+	if (activator->flags & FL_DISGUISED)		// PGM
+		return;									// PGM
+
+// delay reaction so if the monster is teleported, its sound is still heard
+	self->enemy = activator;
+	FoundTarget (self);
+}
+
+
+void monster_start_go (edict_t *self);
+
+
+void monster_triggered_spawn (edict_t *self)
+{
+	self->s.origin[2] += 1;
+	KillBox (self);
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->air_finished = level.time + 12;
+	gi.linkentity (self);
+
+	monster_start_go (self);
+
+	if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
+	{
+		if(!(self->enemy->flags & FL_DISGUISED))		// PGM
+			FoundTarget (self);
+		else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
+			self->enemy = NULL;
+	}
+	else
+	{
+		self->enemy = NULL;
+	}
+}
+
+void monster_triggered_spawn_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	// we have a one frame delay here so we don't telefrag the guy who activated us
+	self->think = monster_triggered_spawn;
+	self->nextthink = level.time + FRAMETIME;
+	if (activator->client)
+		self->enemy = activator;
+	self->use = monster_use;
+}
+
+void monster_triggered_start (edict_t *self)
+{
+	self->solid = SOLID_NOT;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+	self->use = monster_triggered_spawn_use;
+}
+
+
+/*
+================
+monster_death_use
+
+When a monster dies, it fires all of its targets with the current
+enemy as activator.
+================
+*/
+void monster_death_use (edict_t *self)
+{
+	self->flags &= ~(FL_FLY|FL_SWIM);
+	self->monsterinfo.aiflags &= AI_GOOD_GUY;
+
+	if (self->item)
+	{
+		Drop_Item (self, self->item);
+		self->item = NULL;
+	}
+
+	if (self->deathtarget)
+		self->target = self->deathtarget;
+
+	if (!self->target)
+		return;
+
+	G_UseTargets (self, self->enemy);
+}
+
+
+//============================================================================
+
+qboolean monster_start (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return false;
+	}
+
+	if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
+	{
+		self->spawnflags &= ~4;
+		self->spawnflags |= 1;
+//		gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
+	}
+
+	if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+		level.total_monsters++;
+
+	self->nextthink = level.time + FRAMETIME;
+	self->svflags |= SVF_MONSTER;
+	self->s.renderfx |= RF_FRAMELERP;
+	self->takedamage = DAMAGE_AIM;
+	self->air_finished = level.time + 12;
+	self->use = monster_use;
+	self->max_health = self->health;
+	self->clipmask = MASK_MONSTERSOLID;
+
+	self->s.skinnum = 0;
+	self->deadflag = DEAD_NO;
+	self->svflags &= ~SVF_DEADMONSTER;
+
+	if (!self->monsterinfo.checkattack)
+		self->monsterinfo.checkattack = M_CheckAttack;
+	VectorCopy (self->s.origin, self->s.old_origin);
+
+	if (st.item)
+	{
+		self->item = FindItemByClassname (st.item);
+		if (!self->item)
+			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+	}
+
+	// randomize what frame they start on
+	if (self->monsterinfo.currentmove)
+		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
+
+	// PMM - get this so I don't have to do it in all of the monsters
+	self->monsterinfo.base_height = self->maxs[2];
+
+	// PMM - clear these
+	self->monsterinfo.quad_framenum = 0;
+	self->monsterinfo.double_framenum = 0;
+	self->monsterinfo.invincible_framenum = 0;
+
+	return true;
+}
+
+void monster_start_go (edict_t *self)
+{
+	vec3_t	v;
+
+	if (self->health <= 0)
+		return;
+
+	// check for target to combat_point and change to combattarget
+	if (self->target)
+	{
+		qboolean	notcombat;
+		qboolean	fixup;
+		edict_t		*target;
+
+		target = NULL;
+		notcombat = false;
+		fixup = false;
+		while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") == 0)
+			{
+				self->combattarget = self->target;
+				fixup = true;
+			}
+			else
+			{
+				notcombat = true;
+			}
+		}
+		if (notcombat && self->combattarget)
+			gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
+		if (fixup)
+			self->target = NULL;
+	}
+
+	// validate combattarget
+	if (self->combattarget)
+	{
+		edict_t		*target;
+
+		target = NULL;
+		while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") != 0)
+			{
+				gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
+					self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
+					self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
+					(int)target->s.origin[2]);
+			}
+		}
+	}
+
+	if (self->target)
+	{
+		self->goalentity = self->movetarget = G_PickTarget(self->target);
+		if (!self->movetarget)
+		{
+			gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
+			self->target = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+		else if (strcmp (self->movetarget->classname, "path_corner") == 0)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+			self->monsterinfo.walk (self);
+			self->target = NULL;
+		}
+		else
+		{
+			self->goalentity = self->movetarget = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+	}
+	else
+	{
+		self->monsterinfo.pausetime = 100000000;
+		self->monsterinfo.stand (self);
+	}
+
+	self->think = monster_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void walkmonster_start_go (edict_t *self)
+{
+	if (!(self->spawnflags & 2) && level.time < 1)
+	{
+		M_droptofloor (self);
+
+		if (self->groundentity)
+			if (!M_walkmove (self, 0, 0))
+				gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+	}
+	
+	if (!self->yaw_speed)
+		self->yaw_speed = 20;
+	// PMM - stalkers are too short for this
+	if (!(strcmp(self->classname, "monster_stalker")))
+		self->viewheight = 15;
+	else
+		self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void walkmonster_start (edict_t *self)
+{
+	self->think = walkmonster_start_go;
+	monster_start (self);
+}
+
+
+void flymonster_start_go (edict_t *self)
+{
+	if (!M_walkmove (self, 0, 0))
+		gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+
+void flymonster_start (edict_t *self)
+{
+	self->flags |= FL_FLY;
+	self->think = flymonster_start_go;
+	monster_start (self);
+}
+
+
+void swimmonster_start_go (edict_t *self)
+{
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 10;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void swimmonster_start (edict_t *self)
+{
+	self->flags |= FL_SWIM;
+	self->think = swimmonster_start_go;
+	monster_start (self);
+}
+
+//ROGUE
+
+void stationarymonster_start_go (edict_t *self);
+
+void stationarymonster_triggered_spawn (edict_t *self)
+{
+	KillBox (self);
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->air_finished = level.time + 12;
+	gi.linkentity (self);
+
+	// FIXME - why doesn't this happen with real monsters?
+	self->spawnflags &= ~2;
+
+	stationarymonster_start_go (self);
+
+	if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
+	{
+		if(!(self->enemy->flags & FL_DISGUISED))		// PGM
+			FoundTarget (self);
+		else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
+			self->enemy = NULL;
+	}
+	else
+	{
+		self->enemy = NULL;
+	}
+}
+
+void stationarymonster_triggered_spawn_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	// we have a one frame delay here so we don't telefrag the guy who activated us
+	self->think = stationarymonster_triggered_spawn;
+	self->nextthink = level.time + FRAMETIME;
+	if (activator->client)
+		self->enemy = activator;
+	self->use = monster_use;
+}
+
+void stationarymonster_triggered_start (edict_t *self)
+{
+	self->solid = SOLID_NOT;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+	self->use = stationarymonster_triggered_spawn_use;
+}
+
+void stationarymonster_start_go (edict_t *self)
+{
+// PGM - only turrets use this, so remove the error message. They're supposed to be in solid.
+
+//	if (!M_walkmove (self, 0, 0))
+//		gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+	
+	if (!self->yaw_speed)
+		self->yaw_speed = 20;
+//	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		stationarymonster_triggered_start (self);
+}
+
+void stationarymonster_start (edict_t *self)
+{
+	self->think = stationarymonster_start_go;
+	monster_start (self);
+}
+
+void monster_done_dodge (edict_t *self)
+{
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("%s done dodging\n", self->classname);
+	self->monsterinfo.aiflags &= ~AI_DODGING;
+}
+//ROGUE
--- /dev/null
+++ b/rogue/g_newai.c
@@ -1,0 +1,1980 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+//===============================
+// BLOCKED Logic
+//===============================
+
+/*
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_DEBUGTRAIL);
+		gi.WritePosition (pt1);
+		gi.WritePosition (pt2);
+		gi.multicast (pt1, MULTICAST_PVS);	
+
+		self->nextthink = level.time + 10;
+*/
+
+// plat states, copied from g_func.c
+
+#define	STATE_TOP			0
+#define	STATE_BOTTOM		1
+#define STATE_UP			2
+#define STATE_DOWN			3
+
+qboolean face_wall (edict_t *self);
+void HuntTarget (edict_t *self);
+
+// PMM
+qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end);
+
+
+// blocked_checkshot
+//	shotchance: 0-1, chance they'll take the shot if it's clear.
+qboolean blocked_checkshot (edict_t *self, float shotChance)
+{
+	qboolean	playerVisible;
+
+	if(!self->enemy)
+		return false;
+
+	// blocked checkshot is only against players. this will
+	// filter out player sounds and other shit they should
+	// not be firing at.
+	if(!(self->enemy->client))
+		return false;
+
+	if (qrandom() < shotChance)
+		return false;
+
+	// PMM - special handling for the parasite
+	if (!strcmp(self->classname, "monster_parasite"))
+	{
+		vec3_t	f, r, offset, start, end;
+		trace_t	tr;
+		AngleVectors (self->s.angles, f, r, NULL);
+		VectorSet (offset, 24, 0, 6);
+		G_ProjectSource (self->s.origin, offset, f, r, start);
+
+		VectorCopy (self->enemy->s.origin, end);
+		if (!parasite_drain_attack_ok(start, end))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+			if (!parasite_drain_attack_ok(start, end))
+			{
+				end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+				if (!parasite_drain_attack_ok(start, end))
+					return false;
+			}
+		}
+		VectorCopy (self->enemy->s.origin, end);
+
+		tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+		if (tr.ent != self->enemy)
+		{
+			self->monsterinfo.aiflags |= AI_BLOCKED;
+			
+			if(self->monsterinfo.attack)
+				self->monsterinfo.attack(self);
+			
+			self->monsterinfo.aiflags &= ~AI_BLOCKED;
+			return true;
+		}
+	}
+
+	playerVisible = visible (self, self->enemy);
+	// always shoot at teslas
+	if(playerVisible)
+	{
+		if (!strcmp(self->enemy->classname, "tesla"))
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("blocked: taking a shot\n");
+
+			// turn on AI_BLOCKED to let the monster know the attack is being called
+			// by the blocked functions...
+			self->monsterinfo.aiflags |= AI_BLOCKED;
+			
+			if(self->monsterinfo.attack)
+				self->monsterinfo.attack(self);
+			
+			self->monsterinfo.aiflags &= ~AI_BLOCKED;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+// blocked_checkplat
+//	dist: how far they are trying to walk.
+qboolean blocked_checkplat (edict_t *self, float dist)
+{
+	int			playerPosition;
+	trace_t		trace;
+	vec3_t		pt1, pt2;
+	vec3_t		forward;
+	edict_t		*plat;
+
+	if(!self->enemy)
+		return false;
+
+	// check player's relative altitude
+	if(self->enemy->absmin[2] >= self->absmax[2])
+		playerPosition = 1;
+	else if(self->enemy->absmax[2] <= self->absmin[2])
+		playerPosition = -1;
+	else
+		playerPosition = 0;
+
+	// if we're close to the same position, don't bother trying plats.
+	if(playerPosition == 0)
+		return false;
+
+	plat = NULL;
+
+	// see if we're already standing on a plat.
+	if(self->groundentity && self->groundentity != WORLD)
+	{
+		if(!strncmp(self->groundentity->classname, "func_plat", 8))
+			plat = self->groundentity;
+	}
+
+	// if we're not, check to see if we'll step onto one with this move
+	if(!plat)
+	{
+		AngleVectors (self->s.angles, forward, NULL, NULL);
+		VectorMA(self->s.origin, dist, forward, pt1);
+		VectorCopy (pt1, pt2);
+		pt2[2] -= 384;
+
+		trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID);
+		if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
+		{
+			if(!strncmp(trace.ent->classname, "func_plat", 8))
+			{
+				plat = trace.ent;
+			}
+		}
+	}
+
+	// if we've found a plat, trigger it.
+	if(plat && plat->use)
+	{
+		if (playerPosition == 1)
+		{
+			if((self->groundentity == plat && plat->moveinfo.state == STATE_BOTTOM) ||
+				(self->groundentity != plat && plat->moveinfo.state == STATE_TOP))
+			{
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf("player above, and plat will raise. using!\n");
+				plat->use (plat, self, self);
+				return true;			
+			}
+		}
+		else if(playerPosition == -1)
+		{
+			if((self->groundentity == plat && plat->moveinfo.state == STATE_TOP) ||
+				(self->groundentity != plat && plat->moveinfo.state == STATE_BOTTOM))
+			{
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf("player below, and plat will lower. using!\n");
+				plat->use (plat, self, self);
+				return true;
+			}
+		}
+//		if(g_showlogic && g_showlogic->value)
+//			gi.dprintf("hit a plat, not using. ppos: %d   plat: %d\n", playerPosition, plat->moveinfo.state);
+	}
+
+	return false;
+}
+
+// blocked_checkjump
+//	dist: how far they are trying to walk.
+//  maxDown/maxUp: how far they'll ok a jump for. set to 0 to disable that direction.
+qboolean blocked_checkjump (edict_t *self, float, float maxDown, float maxUp)
+{
+	int			playerPosition;
+	trace_t		trace;
+	vec3_t		pt1, pt2;
+	vec3_t		forward, up;
+
+	if(!self->enemy)
+		return false;
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+
+	if(self->enemy->absmin[2] > (self->absmin[2] + 16))
+		playerPosition = 1;
+	else if(self->enemy->absmin[2] < (self->absmin[2] - 16))
+		playerPosition = -1;
+	else
+		playerPosition = 0;
+
+	if(playerPosition == -1 && maxDown)
+	{
+		// check to make sure we can even get to the spot we're going to "fall" from
+		VectorMA(self->s.origin, 48, forward, pt1);
+		trace = gi.trace(self->s.origin, self->mins, self->maxs, pt1, self, MASK_MONSTERSOLID);
+		if(trace.fraction < 1)
+		{
+//			gi.dprintf("can't get thar from hear...\n");
+			return false;
+		}
+
+		VectorCopy (pt1, pt2);
+		pt2[2] = self->mins[2] - maxDown - 1;
+
+		trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER);
+		if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
+		{
+			if((self->absmin[2] - trace.endpos[2]) >= 24 && trace.contents & MASK_SOLID)
+			{
+				if( (self->enemy->absmin[2] - trace.endpos[2]) > 32)
+				{
+//					if(g_showlogic && g_showlogic->value)
+//						gi.dprintf("That'll take me too far down...%0.1f\n", (self->enemy->absmin[2] - trace.endpos[2]));
+					return false;
+				}	
+
+				if(trace.plane.normal[2] < 0.9)
+				{
+//					gi.dprintf("Floor angle too much! %s\n", vtos(trace.plane.normal));
+					return false;
+				}
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf("Geronimo! %0.1f\n", (self->absmin[2] - trace.endpos[2]));
+				return true;
+			}
+//			else if(g_showlogic && g_showlogic->value)
+//			{
+//				if(!(trace.contents & MASK_SOLID))
+//					gi.dprintf("Ooooh... Bad stuff down there...\n");
+//				else
+//					gi.dprintf("Too far to fall\n");
+//			}
+		}
+//		else if(g_showlogic && g_showlogic->value)
+//			gi.dprintf("Ooooh... Too far to fall...\n");
+	}
+	else if(playerPosition == 1 && maxUp)
+	{
+		VectorMA(self->s.origin, 48, forward, pt1);
+		VectorCopy(pt1, pt2);
+		pt1[2] = self->absmax[2] + maxUp;
+
+		trace = gi.trace(pt1, vec3_origin, vec3_origin, pt2, self, MASK_MONSTERSOLID | MASK_WATER);
+		if(trace.fraction < 1 && !trace.allsolid && !trace.startsolid)
+		{
+			if((trace.endpos[2] - self->absmin[2]) <= maxUp && trace.contents & MASK_SOLID)
+			{
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf("Jumping Up! %0.1f\n", (trace.endpos[2] - self->absmin[2]));
+				
+				face_wall(self);
+				return true;
+			}
+//			else if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("Too high to jump %0.1f\n", (trace.endpos[2] - self->absmin[2]));
+		}
+//		else if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("Not something I could jump onto\n");
+	}
+//	else if(g_showlogic && g_showlogic->value)
+//		gi.dprintf("Player at similar level. No need to jump up?\n");
+
+	return false;
+}
+
+// checks to see if another coop player is nearby, and will switch.
+qboolean blocked_checknewenemy (edict_t *)
+{
+/*
+	int		player;
+	edict_t *ent;
+
+	if (!(coop->value))
+		return false;
+
+	for (player = 1; player <= game.maxclients; player++)
+	{
+		ent = &g_edicts[player];
+		if (!ent->inuse)
+			continue;
+		if (!ent->client)
+			continue;
+		if (ent == self->enemy)
+			continue;
+
+		if (visible (self, ent))
+		{
+			if (g_showlogic && g_showlogic->value)
+				gi.dprintf ("B_CNE: %s acquired new enemy %s\n", self->classname, ent->client->pers.netname);
+
+			self->enemy = ent;
+			FoundTarget (self);
+			return true;
+		}
+	}
+
+	return false;
+*/
+	return false;
+}
+
+// *************************
+// HINT PATHS
+// *************************
+
+#define HINT_ENDPOINT		0x0001
+#define	MAX_HINT_CHAINS		100
+
+int	hint_paths_present;
+edict_t *hint_path_start[MAX_HINT_CHAINS];
+int	num_hint_paths;
+
+//
+// AI code
+//
+
+// =============
+// hintpath_findstart - given any hintpath node, finds the start node
+// =============
+edict_t	*hintpath_findstart(edict_t *ent)
+{
+	edict_t		*e;
+	edict_t		*last = WORLD;
+	int			field;
+
+	if(ent->target)		// starting point
+	{
+		field = FOFS(targetname);
+		e = G_Find(NULL, field, ent->target);
+		while(e)
+		{
+			last = e;
+			if(!e->target)
+				break;
+			e = G_Find(NULL, field, e->target);
+		}
+	}
+	else				// end point
+	{
+		field = FOFS(target);
+		e = G_Find(NULL, field, ent->targetname);
+		while(e)
+		{
+			last = e;
+			if(!e->targetname)
+				break;
+			e = G_Find(NULL, field, e->targetname);
+		}
+	}
+
+	if(!(last->spawnflags & HINT_ENDPOINT))
+	{
+//		gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
+		return nil;
+	}
+
+	if(last == WORLD)
+		last = nil;
+	return last;
+}
+
+// =============
+// hintpath_other_end - given one endpoint of a hintpath, returns the other end.
+// =============
+edict_t	*hintpath_other_end(edict_t *ent)
+{
+	edict_t		*e;
+	edict_t		*last = WORLD;
+	int			field;
+
+	if(ent->target)		// starting point
+	{
+		field = FOFS(targetname);
+		e = G_Find(NULL, field, ent->target);
+		while(e)
+		{
+			last = e;
+			if(!e->target)
+				break;
+			e = G_Find(NULL, field, e->target);
+		}
+	}
+	else				// end point
+	{
+		field = FOFS(target);
+		e = G_Find(NULL, field, ent->targetname);
+		while(e)
+		{
+			last = e;
+			if(!e->targetname)
+				break;
+			e = G_Find(NULL, field, e->targetname);
+		}
+	}
+
+	if(!(last->spawnflags & HINT_ENDPOINT))
+	{
+//		gi.dprintf ("end of chain is not HINT_ENDPOINT\n");
+		return nil;
+	}
+
+	if(last == WORLD)
+		last = nil;
+	return last;
+}
+
+// =============
+// hintpath_go - starts a monster (self) moving towards the hintpath (point)
+//		disables all contrary AI flags.
+// =============
+void hintpath_go (edict_t *self, edict_t *point)
+{
+	vec3_t	dir;
+	vec3_t	angles;
+
+	VectorSubtract(point->s.origin, self->s.origin, dir);
+	vectoangles2(dir, angles);
+
+	self->ideal_yaw = angles[YAW];
+	self->goalentity = self->movetarget = point;
+	self->monsterinfo.pausetime = 0;
+	self->monsterinfo.aiflags |= AI_HINT_PATH;
+	self->monsterinfo.aiflags &= ~(AI_SOUND_TARGET | AI_PURSUIT_LAST_SEEN | AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+	// run for it
+	self->monsterinfo.search_time = level.time;
+	self->monsterinfo.run (self);
+}
+
+// =============
+// hintpath_stop - bails a monster out of following hint paths
+// =============
+void hintpath_stop (edict_t *self)
+{
+	self->goalentity = NULL;
+	self->movetarget = NULL;
+//	self->monsterinfo.last_hint = NULL;
+	self->monsterinfo.last_hint_time = level.time;
+	self->monsterinfo.goal_hint = NULL;
+	self->monsterinfo.aiflags &= ~AI_HINT_PATH;
+	if (has_valid_enemy(self))
+	{
+		// if we can see our target, go nuts
+		if (visible(self, self->enemy))
+		{
+			FoundTarget (self);
+			return;
+		}
+		// otherwise, keep chasing
+		HuntTarget (self);
+		return;
+	}
+	// if our enemy is no longer valid, forget about our enemy and go into stand
+	self->enemy = NULL;
+		// we need the pausetime otherwise the stand code
+		// will just revert to walking with no target and
+		// the monsters will wonder around aimlessly trying
+		// to hunt the world entity
+	self->monsterinfo.pausetime = level.time + 100000000;
+	self->monsterinfo.stand (self);
+}
+
+// =============
+// monsterlost_checkhint - the monster (self) will check around for valid hintpaths.
+//		a valid hintpath is one where the two endpoints can see both the monster
+//		and the monster's enemy. if only one person is visible from the endpoints,
+//		it will not go for it.
+// =============
+qboolean monsterlost_checkhint2 (edict_t *self);
+
+qboolean monsterlost_checkhint (edict_t *self)
+{
+	edict_t		*e, *monster_pathchain, *target_pathchain, *checkpoint = nil;
+	edict_t		*closest;
+	float		closest_range = 1000000;
+	edict_t		*start, *destination;
+	int			count1=0, count2=0, count4=0, count5=0;
+	float		r;
+	int			i;
+	qboolean	hint_path_represented[MAX_HINT_CHAINS];
+
+	// if there are no hint paths on this map, exit immediately.
+	if(!hint_paths_present)
+		return false;
+
+	if(!self->enemy)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return false;
+	
+	if (!strcmp(self->classname, "monster_turret"))
+		return false;
+
+	monster_pathchain = NULL;
+
+	// find all the hint_paths.
+	// FIXME - can we not do this every time?
+	for (i=0; i < num_hint_paths; i++)
+	{
+		e = hint_path_start[i];
+		while(e)
+		{
+			count1++;
+			if (e->monster_hint_chain)
+			{
+//				gi.dprintf ("uh, oh, I didn't clean up after myself\n");
+				e->monster_hint_chain = NULL;
+			}
+			if (monster_pathchain)
+			{
+				checkpoint->monster_hint_chain = e;
+				checkpoint = e;
+			}
+			else
+			{
+				monster_pathchain = e;
+				checkpoint = e;
+			}
+			e = e->hint_chain;
+		}
+	}
+	
+	// filter them by distance and visibility to the monster
+	e = monster_pathchain;
+	checkpoint = NULL;
+	while (e)
+	{
+		r = realrange (self, e);
+
+//		if (r > 512)
+//			count3++;
+
+		if (r > 512)
+		{
+			count2++;
+//			if (g_showlogic && g_showlogic->value)
+//			{
+//				gi.dprintf ("MONSTER (%s) DISTANCE:  ", self->classname);
+//				if (e->targetname)
+//					gi.dprintf ("targetname %s\n", e->targetname);
+//				else
+//					gi.dprintf ("start -> %s\n", e->target);
+//			}
+			if (checkpoint)
+			{
+				checkpoint->monster_hint_chain = e->monster_hint_chain;
+				e->monster_hint_chain = NULL;
+				e = checkpoint->monster_hint_chain;
+				continue;
+			}
+			else
+			{
+				// use checkpoint as temp pointer
+				checkpoint = e;
+				e = e->monster_hint_chain;
+				checkpoint->monster_hint_chain = NULL;
+				// and clear it again
+				checkpoint = NULL;
+				// since we have yet to find a valid one (or else checkpoint would be set) move the
+				// start of monster_pathchain
+				monster_pathchain = e;
+				continue;
+			}
+		}
+		if (!visible(self, e))
+		{
+			count4++;
+//			if (g_showlogic && g_showlogic->value)
+//			{
+//				gi.dprintf ("MONSTER (%s) VISIBILITY:  ", self->classname);
+//				if (e->targetname)
+//					gi.dprintf ("targetname %s\n", e->targetname);
+//				else
+//					gi.dprintf ("start -> %s\n", e->target);
+//			}
+			if (checkpoint)
+			{
+				checkpoint->monster_hint_chain = e->monster_hint_chain;
+				e->monster_hint_chain = NULL;
+				e = checkpoint->monster_hint_chain;
+				continue;
+			}
+			else
+			{
+				// use checkpoint as temp pointer
+				checkpoint = e;
+				e = e->monster_hint_chain;
+				checkpoint->monster_hint_chain = NULL;
+				// and clear it again
+				checkpoint = NULL;
+				// since we have yet to find a valid one (or else checkpoint would be set) move the
+				// start of monster_pathchain
+				monster_pathchain = e;
+				continue;
+			}
+		}
+		// if it passes all the tests, it's a keeper
+//		if (g_showlogic && g_showlogic->value)
+//		{
+//			gi.dprintf ("MONSTER (%s) ACCEPT:  ", self->classname);
+//			if (e->targetname)
+//				gi.dprintf ("targetname %s\n", e->targetname);
+//			else
+//				gi.dprintf ("start -> %s\n", e->target);
+//		}
+		count5++;
+		checkpoint = e;
+		e = e->monster_hint_chain;
+	}
+
+	// at this point, we have a list of all of the eligible hint nodes for the monster
+	// we now take them, figure out what hint chains they're on, and traverse down those chains,
+	// seeing whether any can see the player
+	//
+	// first, we figure out which hint chains we have represented in monster_pathchain
+	if (count5 == 0)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("No eligible hint paths found.\n");
+		return false;
+	}
+
+	for (i=0; i < num_hint_paths; i++)
+	{
+		hint_path_represented[i] = false;
+	}
+	e = monster_pathchain;
+	while (e)
+	{
+		if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
+		{
+//			if (g_showlogic && g_showlogic->value)
+//				gi.dprintf ("bad hint_chain_id! %d\n", e->hint_chain_id);
+			return false;
+		}
+		hint_path_represented[e->hint_chain_id] = true;
+		e = e->monster_hint_chain;
+	}
+
+	count2 = 0;
+	count4 = 0;
+	count5 = 0;
+
+	// now, build the target_pathchain which contains all of the hint_path nodes we need to check for
+	// validity (within range, visibility)
+	target_pathchain = NULL;
+	checkpoint = NULL;
+	for (i=0; i < num_hint_paths; i++)
+	{
+		// if this hint chain is represented in the monster_hint_chain, add all of it's nodes to the target_pathchain
+		// for validity checking
+		if (hint_path_represented[i])
+		{
+			e = hint_path_start[i];
+			while (e)
+			{
+				if (target_pathchain)
+				{
+					checkpoint->target_hint_chain = e;
+					checkpoint = e;
+				}
+				else
+				{
+					target_pathchain = e;
+					checkpoint = e;
+				}
+				e = e->hint_chain;
+			}
+		}
+	}
+
+	// target_pathchain is a list of all of the hint_path nodes we need to check for validity relative to the target
+	e = target_pathchain;
+	checkpoint = NULL;
+	while (e)
+	{
+		r = realrange (self->enemy, e);
+
+//		if (r > 512)
+//			count3++;
+
+		if (r > 512)
+		{
+			count2++;
+//			if (g_showlogic && g_showlogic->value)
+//			{
+//				gi.dprintf ("TARGET RANGE:  ");
+//				if (e->targetname)
+//					gi.dprintf ("targetname %s\n", e->targetname);
+//				else
+//					gi.dprintf ("start -> %s\n", e->target);
+//			}
+			if (checkpoint)
+			{
+				checkpoint->target_hint_chain = e->target_hint_chain;
+				e->target_hint_chain = NULL;
+				e = checkpoint->target_hint_chain;
+				continue;
+			}
+			else
+			{
+				// use checkpoint as temp pointer
+				checkpoint = e;
+				e = e->target_hint_chain;
+				checkpoint->target_hint_chain = NULL;
+				// and clear it again
+				checkpoint = NULL;
+				target_pathchain = e;
+				continue;
+			}
+		}
+		if (!visible(self->enemy, e))
+		{
+			count4++;
+//			if (g_showlogic && g_showlogic->value)
+//			{
+//				gi.dprintf ("TARGET VISIBILITY:  ");
+//				if (e->targetname)
+//					gi.dprintf ("targetname %s\n", e->targetname);
+//				else
+//					gi.dprintf ("start -> %s\n", e->target);
+//			}
+			if (checkpoint)
+			{
+				checkpoint->target_hint_chain = e->target_hint_chain;
+				e->target_hint_chain = NULL;
+				e = checkpoint->target_hint_chain;
+				continue;
+			}
+			else
+			{
+				// use checkpoint as temp pointer
+				checkpoint = e;
+				e = e->target_hint_chain;
+				checkpoint->target_hint_chain = NULL;
+				// and clear it again
+				checkpoint = NULL;
+				target_pathchain = e;
+				continue;
+			}
+		}
+		// if it passes all the tests, it's a keeper
+//		if (g_showlogic && g_showlogic->value)
+//		{
+//			gi.dprintf ("TARGET ACCEPT:  ");
+//			if (e->targetname)
+//				gi.dprintf ("targetname %s\n", e->targetname);
+//			else
+//				gi.dprintf ("start -> %s\n", e->target);
+//		}
+		count5++;
+		checkpoint = e;
+		e = e->target_hint_chain;
+	}
+	
+	// at this point we should have:
+	// monster_pathchain - a list of "monster valid" hint_path nodes linked together by monster_hint_chain
+	// target_pathcain - a list of "target valid" hint_path nodes linked together by target_hint_chain.  these
+	//                   are filtered such that only nodes which are on the same chain as "monster valid" nodes
+	//
+	// Now, we figure out which "monster valid" node we want to use
+	// 
+	// To do this, we first off make sure we have some target nodes.  If we don't, there are no valid hint_path nodes
+	// for us to take
+	//
+	// If we have some, we filter all of our "monster valid" nodes by which ones have "target valid" nodes on them
+	//
+	// Once this filter is finished, we select the closest "monster valid" node, and go to it.
+
+	if (count5 == 0)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("No valid target nodes found\n");
+		return false;
+	}
+
+	// reuse the hint_chain_represented array, this time to see which chains are represented by the target
+	for (i=0; i < num_hint_paths; i++)
+	{
+		hint_path_represented[i] = false;
+	}
+
+	e = target_pathchain;
+	while (e)
+	{
+		if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths))
+		{
+//			gi.dprintf ("bad hint_chain_id! %d\n", e->hint_chain_id);
+			return false;
+		}
+		hint_path_represented[e->hint_chain_id] = true;
+		e = e->target_hint_chain;
+	}
+	
+	// traverse the monster_pathchain - if the hint_node isn't represented in the "target valid" chain list, 
+	// remove it
+	// if it is on the list, check it for range from the monster.  If the range is the closest, keep it
+	//
+	closest = NULL;
+	e = monster_pathchain;
+	while (e)
+	{
+		if (!(hint_path_represented[e->hint_chain_id]))
+		{
+			checkpoint = e->monster_hint_chain;
+			e->monster_hint_chain = NULL;
+			e = checkpoint;
+			continue;
+		}
+		r = realrange(self, e);
+		if (r < closest_range)
+			closest = e;
+		e = e->monster_hint_chain;
+	}
+
+	if (!closest)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Failed to find closest node for monster.  Shouldn't happen.\n");
+		return false;
+	}
+
+	start = closest;
+	// now we know which one is the closest to the monster .. this is the one the monster will go to
+	// we need to finally determine what the DESTINATION node is for the monster .. walk down the hint_chain,
+	// and find the closest one to the player
+
+	closest = NULL;
+	closest_range = 10000000;
+	e = target_pathchain;
+	while (e)
+	{
+		if (start->hint_chain_id == e->hint_chain_id)
+		{
+			r = realrange(self, e);
+			if (r < closest_range)
+				closest = e;
+		}
+		e = e->target_hint_chain;
+	}
+
+	if (!closest)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Failed to find closest node for target.  Shouldn't happen.\n");
+		return false;
+	}
+	
+	destination = closest;
+
+	self->monsterinfo.goal_hint = destination;
+//	self->monsterinfo.last_hint = NULL;
+	hintpath_go(self, start);
+
+//	if(g_showlogic && g_showlogic->value)
+//	{
+//		gi.dprintf ("found path.  proceed to ");
+//		if (start->targetname)
+//			gi.dprintf ("%s to get to ", start->targetname);
+//		else
+//			gi.dprintf ("start (->%s) to get to ", start->target);
+//		if (destination->targetname)
+//			gi.dprintf ("%s.", destination->targetname);
+//		else
+//			gi.dprintf ("start (->%s)", destination->target);
+//	}
+//		gi.dprintf("found path. proceed to %s to get to %s\n", vtos(start->s.origin), vtos(destination->s.origin));
+
+	return true;
+}
+/*
+qboolean monsterlost_checkhint2 (edict_t *self)
+{
+	edict_t		*e, *e2, *goPoint;
+	int			field;
+	int			playerVisible, selfVisible;
+
+	// if there are no hint paths on this map, exit immediately.
+	if(!hint_paths_present)
+		return false;
+
+	if(!self->enemy)
+		return false;
+
+	goPoint = NULL;
+	field = FOFS(classname);
+	
+	// check all the hint_paths.
+	e = G_Find(NULL, field, "hint_path");
+	while(e)
+	{
+		// if it's an endpoint, check for "validity"
+		if(e->spawnflags & HINT_ENDPOINT)
+		{
+			// check visibility from this spot
+			selfVisible = visible(e, self);
+			playerVisible = visible(e, self->enemy);
+//			gi.dprintf("checking endpoint at %s %d %d\n", vtos(e->s.origin),selfVisible,playerVisible);
+
+			// at least one of us is visible from this endpoint.
+			// now check the other one if needed.
+			if(selfVisible || playerVisible)
+			{
+				// if endpoint 1 saw me, set my destination to it.
+				if(selfVisible)
+					goPoint = e;
+
+				// if both aren't visible, try the other endpoint
+				if(!selfVisible || !playerVisible)
+				{
+					e2 = hintpath_other_end(e);
+					if(!e2)		// could not connect to the other endpoint
+					{
+						gi.dprintf("Unlinked hint paths!\n");
+						return false;
+					}
+
+					// if endpoint 1 saw the enemy, see if endpoint 2 sees me
+					if(!selfVisible)
+						selfVisible = visible(e2, self);
+					// if endpoint 1 saw me, see if endpoint 2 sees the enemy
+					else if(!playerVisible)
+						playerVisible = visible(e2, self->enemy);
+
+					// if endpoint 2 saw me, set my destination to it.
+					if(!goPoint && selfVisible)
+						goPoint = e2;
+
+//					gi.dprintf("checking other endpoint at %s %d %d\n", vtos(e2->s.origin),selfVisible,playerVisible);
+				}
+
+				// if both are visible from at least one endpoint,
+				// go for it.
+				if(selfVisible && playerVisible)
+				{
+					// set me to go to goPoint
+					if(g_showlogic && g_showlogic->value)
+						gi.dprintf("found path. proceed to %s\n", vtos(goPoint->s.origin));
+					
+					// since this is a new hint path trip, set last_hint to NULL
+					self->monsterinfo.last_hint = NULL;
+					hintpath_go(self, goPoint);
+					return true;
+				}
+			}
+		}
+		e = G_Find(e, field, "hint_path");
+	}
+
+	// if we got here, we didn't find a valid path
+	if(g_showlogic && g_showlogic->value)
+		gi.dprintf("blocked_checkhint: found no paths\n");
+	return false;
+}
+*/
+//
+// Path code
+//
+
+// =============
+// hint_path_touch - someone's touched the hint_path
+// =============
+void hint_path_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t		*e, *goal, *next = nil;
+//	int			chain;			 // direction - (-1) = upstream, (1) = downstream, (0) = done
+	qboolean	goalFound = false;
+
+	// make sure we're the target of it's obsession
+	if(other->movetarget == self)
+	{
+		goal = other->monsterinfo.goal_hint;
+		
+		// if the monster is where he wants to be
+		if (goal == self)
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("Got to goal, detatching\n");
+			hintpath_stop (other);
+			return;
+		}
+		else
+		{
+			// if we aren't, figure out which way we want to go
+			e = hint_path_start[self->hint_chain_id];
+			while (e)
+			{
+				// if we get up to ourselves on the hint chain, we're going down it
+				if (e == self)
+				{
+					next = e->hint_chain;
+					break;
+				}
+				if (e == goal)
+					goalFound = true;
+				// if we get to where the next link on the chain is this hint_path and have found the goal on the way
+				// we're going upstream, so remember who the previous link is
+				if ((e->hint_chain == self) && goalFound)
+				{
+					next = e;
+					break;
+				}
+				e = e->hint_chain;
+			}
+		}
+
+		// if we couldn't find it, have the monster go back to normal hunting.
+		if(!next)
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("couldn't figure out next node, dropping hint path\n");
+			hintpath_stop(other);
+			return;
+		}
+
+		// set the last_hint entry to this hint_path, and
+		// send him on his way
+//		other->monsterinfo.last_hint = self;
+//		if(g_showlogic && g_showlogic->value)
+//		{
+//			gi.dprintf("moving to next point, ");
+//			if (next->targetname)
+//				gi.dprintf ("targetname %s\n", next->targetname);
+//			else
+//				gi.dprintf ("start -> %s\n", next->target);
+//		}
+		hintpath_go(other, next);
+
+		// have the monster freeze if the hint path we just touched has a wait time
+		// on it, for example, when riding a plat.
+		if(self->wait)
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("monster waiting %0.1f\n", self->wait);
+			other->nextthink = level.time + self->wait;
+		}
+	}
+}
+/*
+void hint_path_touch2 (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	edict_t		*next, *last;
+	int			chain;
+
+	// make sure we're the target of it's obsession
+	if(other->movetarget == self)
+	{
+		chain = 0;		// direction the monster is going in the chain
+		next = NULL;	// next hint_path
+
+//		gi.dprintf("hint_path %s\n", vtos(self->s.origin));
+		// is this the first hintpath targeted? if so, we can do this easily.
+		if(other->monsterinfo.last_hint == NULL)
+		{
+			if(self->target)		// forward chaining
+				chain = 1;
+			else					// backward chaining
+				chain = -1;
+		}
+		else
+		{
+			// shortcut to last_hint
+			last = other->monsterinfo.last_hint;
+
+			// make sure it's valid...
+			if ( (last < g_edicts) || (last >= &g_edicts[globals.num_edicts]))
+			{
+				if(g_showlogic && g_showlogic->value)
+				{
+					gi.dprintf("bogus last_hint encountered.\n");
+					gi.dprintf("detaching from hint path %d\n", chain);
+				}
+				hintpath_stop (other);
+				return;
+			}
+			
+			// if we're an endpoint, then the monster is done moving.
+			if(self->spawnflags & HINT_ENDPOINT)
+			{
+				chain = 0;
+			}
+			// if last hint's target is our targetname, it's forward chaining.
+			else if(last->target && self->targetname && !strcmp(last->target, self->targetname))
+			{
+				chain = 1;
+			}
+			// if last hint's targetname is our target, it's backward chaining.
+			// FIXME - last->targetname was 1, not NULL ????  was a screwed up hintpath
+			else if(self->target && last->targetname && !strcmp(last->targetname, self->target))
+			{
+				chain = -1;
+			}
+			else	// if it gets here, i'm not sure how
+			{
+				gi.dprintf("hit an uncovered possibility in hint_path_touch\n");
+				chain = 0;
+			}
+		}
+
+		// find the "next" hint_path
+		if(chain == 1 && self->target)						// forward chaining
+			next = G_Find(NULL, FOFS(targetname), self->target);
+		else if(chain == -1 && self->targetname)			// backward chaining
+			next = G_Find(NULL, FOFS(target), self->targetname);
+
+		// if we couldn't find it, have the monster go back to normal hunting.
+		if(!next)
+		{
+			if(g_showlogic && g_showlogic->value)
+				gi.dprintf("detaching from hint path %d\n", chain);
+			hintpath_stop(other);
+			return;
+		}
+
+		// set the last_hint entry to this hint_path, and
+		// send him on his way
+		other->monsterinfo.last_hint = self;
+		if(g_showlogic && g_showlogic->value)
+			gi.dprintf("moving to next point, %s\n", vtos(next->s.origin));
+		hintpath_go(other, next);
+
+		// have the monster freeze if the hint path we just touched has a wait time
+		// on it, for example, when riding a plat.
+		if(self->wait)
+		{
+			if(g_showlogic && g_showlogic->value)
+				gi.dprintf("monster waiting %0.1f\n", self->wait);
+			other->nextthink = level.time + self->wait;
+		}
+	}
+}
+*/
+
+/*QUAKED hint_path (.5 .3 0) (-8 -8 -8) (8 8 8) END
+Target: next hint path
+
+END - set this flag on the endpoints of each hintpath.
+
+"wait" - set this if you want the monster to freeze when they touch this hintpath
+*/
+void SP_hint_path (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict(self);
+		return;
+	}
+
+	if (!self->targetname && !self->target)
+	{
+		gi.dprintf ("unlinked hint_path at %s\n", vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = hint_path_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags |= SVF_NOCLIENT;
+	gi.linkentity (self);
+}
+
+//int	hint_paths_present;
+//edict_t *hint_path_start[100];
+//int	num_hint_paths;
+
+// ============
+// InitHintPaths - Called by InitGame (g_save) to enable quick exits if valid
+// ============
+void InitHintPaths (void)
+{
+	edict_t		*e, *current;
+	int			field, i, count2;
+
+	hint_paths_present = 0;
+	
+	// check all the hint_paths.
+	field = FOFS(classname);
+	e = G_Find(NULL, field, "hint_path");
+	if(e)
+	{
+//		gi.dprintf("hint paths present on map\n");
+		hint_paths_present = 1;
+	}
+	else
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("hint paths not present on map\n");
+		return;
+	}
+
+	memset (hint_path_start, 0, MAX_HINT_CHAINS*sizeof (edict_t *));
+	num_hint_paths = 0;
+	while(e)
+	{
+		if(e->spawnflags & HINT_ENDPOINT)
+		{
+			if (e->target) // start point
+			{
+				if (e->targetname) // this is a bad end, ignore it
+				{
+					gi.dprintf ("Hint path at %s marked as endpoint with both target (%s) and targetname (%s)\n",
+						vtos (e->s.origin), e->target, e->targetname);
+				}
+				else
+				{
+					if (num_hint_paths >= MAX_HINT_CHAINS)
+					{
+//						gi.dprintf ("Only %d hint chains allowed.  Connect some together!\n", MAX_HINT_CHAINS);
+						break;
+					}
+					hint_path_start[num_hint_paths++] = e;
+				}
+			}
+		}
+		e = G_Find(e, field, "hint_path");
+	}
+
+	field = FOFS(targetname);
+	for (i=0; i< num_hint_paths; i++)
+	{
+		count2 = 1;
+		current = hint_path_start[i];
+		current->hint_chain_id = i;
+//		gi.dprintf ("start ");
+		e = G_Find(NULL, field, current->target);
+		if (G_Find(e, field, current->target))
+		{
+			gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n", 
+				vtos (current->s.origin), num_hint_paths, current->target);
+			hint_path_start[i]->hint_chain = NULL;
+			continue;
+		}
+		while (e)
+		{
+			if (e->hint_chain)
+			{
+				gi.dprintf ("\nCircular hint path at %s detected for chain %d, targetname %s\n", 
+					vtos (e->s.origin), num_hint_paths, e->targetname);
+				hint_path_start[i]->hint_chain = NULL;
+				break;
+			}
+			count2++;
+			current->hint_chain = e;
+			current = e;
+			current->hint_chain_id = i;
+//			gi.dprintf ("-> %s ", current->targetname);
+			if (!current->target)
+				break;
+			e = G_Find(NULL, field, current->target);
+			if (G_Find(e, field, current->target))
+			{
+				gi.dprintf ("\nForked hint path at %s detected for chain %d, target %s\n", 
+					vtos (current->s.origin), num_hint_paths, current->target);
+				hint_path_start[i]->hint_chain = NULL;
+				break;
+			}
+		}
+//		if ((g_showlogic) && (g_showlogic->value))
+//			if (count2)
+//			{
+//				goodcount++;
+//				gi.dprintf ("\nhint_path #%d, %d elements\n\n", i, count2);
+//			}
+//			else
+//				gi.dprintf ("\nhint_path #%d invalid\n\n", i);
+	}
+//	if (errors)
+//		gi.error ("hint_path processing failed, fix errors\n");
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("hint_path processing done, %d hint paths linked\n", num_hint_paths);
+}
+
+// *****************************
+//	MISCELLANEOUS STUFF
+// *****************************
+
+// PMM - inback
+// use to see if opponent is behind you (not to side)
+// if it looks a lot like infront, well, there's a reason
+
+qboolean inback (edict_t *self, edict_t *other)
+{
+	vec3_t	vec;
+	float	dot;
+	vec3_t	forward;
+	
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	dot = DotProduct (vec, forward);
+	
+	if (dot < -0.3)
+		return true;
+	return false;
+}
+
+float realrange (edict_t *self, edict_t *other)
+{
+	vec3_t dir;
+	
+	VectorSubtract (self->s.origin, other->s.origin, dir);
+
+	return VectorLength(dir);
+}
+
+qboolean face_wall (edict_t *self)
+{
+	vec3_t	pt;
+	vec3_t	forward;
+	vec3_t	ang;
+	trace_t	tr;
+
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorMA(self->s.origin, 64, forward, pt);
+	tr = gi.trace(self->s.origin, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+	if(tr.fraction < 1 && !tr.allsolid && !tr.startsolid)
+	{
+		vectoangles2(tr.plane.normal, ang);
+		self->ideal_yaw = ang[YAW] + 180;
+		if(self->ideal_yaw > 360)
+			self->ideal_yaw -= 360;
+
+//		if(g_showlogic && g_showlogic->value)
+//			gi.dprintf("facing wall, dir %0.1f/%0.1f\n", ang[YAW], self->ideal_yaw);
+		M_ChangeYaw(self);
+		return true;
+	}
+
+	return false;
+}
+
+//
+// Monster "Bad" Areas
+// 
+
+void badarea_touch (edict_t */*ent*/, edict_t *, cplane_t *, csurface_t *)
+{
+//	drawbbox(ent);
+}
+
+edict_t *SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner)
+{
+	edict_t *badarea;
+	vec3_t	origin;
+	
+	VectorAdd(mins, maxs, origin);
+	VectorScale(origin, 0.5, origin);
+
+	VectorSubtract(maxs, origin, maxs);
+	VectorSubtract(mins, origin, mins);
+
+	badarea = G_Spawn();
+	VectorCopy(origin, badarea->s.origin);
+	VectorCopy(maxs, badarea->maxs);
+	VectorCopy(mins, badarea->mins);
+	badarea->touch = badarea_touch;
+	badarea->movetype = MOVETYPE_NONE;
+	badarea->solid = SOLID_TRIGGER;
+	badarea->classname = "bad_area";
+	gi.linkentity (badarea);
+
+//	gi.dprintf("(%s)-(%s)\n", vtos(badarea->absmin), vtos(badarea->absmax));
+
+	if(lifespan)
+	{
+		badarea->think = G_FreeEdict;
+		badarea->nextthink = level.time + lifespan;
+	}
+	if(owner)
+	{
+		badarea->owner = owner;
+	}
+
+//	drawbbox(badarea);
+	return badarea;
+}
+
+// CheckForBadArea
+//		This is a customized version of G_TouchTriggers that will check
+//		for bad area triggers and return them if they're touched.
+edict_t *CheckForBadArea(edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+	vec3_t		mins, maxs;
+
+	VectorAdd(ent->s.origin, ent->mins, mins);
+	VectorAdd(ent->s.origin, ent->maxs, maxs);
+
+	num = gi.BoxEdicts (mins, maxs, touch, MAX_EDICTS, AREA_TRIGGERS);
+
+//	drawbbox(ent);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (hit->touch == badarea_touch)
+		{
+			return hit;
+		}
+	}
+	
+	return NULL;
+}
+
+#define TESLA_DAMAGE_RADIUS		128
+
+qboolean MarkTeslaArea(edict_t *self, edict_t *tesla)
+{
+	vec3_t	mins, maxs;
+	edict_t *e;
+	edict_t *tail;
+	edict_t *area;
+
+	if(!tesla || !self)
+		return false;
+
+	// make sure this tesla doesn't have a bad area around it already...
+	e = tesla->teamchain;
+	tail = tesla;
+	while (e)
+	{
+		tail = tail->teamchain;
+		if(!strcmp(e->classname, "bad_area"))
+		{
+//			gi.dprintf("tesla already has a bad area marked\n");
+			return false;
+		}
+		e = e->teamchain;
+	}
+
+	// see if we can grab the trigger directly
+	if(tesla->teamchain && tesla->teamchain->inuse)
+	{
+		edict_t *trigger;
+
+		trigger = tesla->teamchain;
+
+//		VectorAdd (trigger->s.origin, trigger->mins, mins);
+//		VectorAdd (trigger->s.origin, trigger->maxs, maxs);
+		VectorCopy(trigger->absmin, mins);
+		VectorCopy(trigger->absmax, maxs);
+
+		if(tesla->air_finished)
+			area = SpawnBadArea (mins, maxs, tesla->air_finished, tesla);
+		else
+			area = SpawnBadArea (mins, maxs, tesla->nextthink, tesla);
+	}
+	// otherwise we just guess at how long it'll last.
+	else
+	{
+	
+		VectorSet (mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, tesla->mins[2]);
+		VectorSet (maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
+
+		area = SpawnBadArea(mins, maxs, 30, tesla);
+	}
+
+	// if we spawned a bad area, then link it to the tesla
+	if(area)
+	{
+//		gi.dprintf("bad area marker spawned and linked to tesla\n");
+		tail->teamchain = area;
+	}
+	return true;
+}
+
+// predictive calculator
+// target is who you want to shoot
+// start is where the shot comes from
+// bolt_speed is how fast the shot is
+// eye_height is a boolean to say whether or not to adjust to targets eye_height
+// offset is how much time to miss by
+// aimdir is the resulting aim direction (pass in NULL if you don't want it)
+// aimpoint is the resulting aimpoint (pass in NULL if don't want it)
+void PredictAim (edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height, float offset, vec3_t aimdir, vec3_t aimpoint)
+{
+	vec3_t dir, vec;
+	float dist, time;
+
+	if (!target || !target->inuse)
+	{
+		VectorCopy (vec3_origin, aimdir);
+		return;
+	}
+
+	VectorSubtract(target->s.origin, start, dir);
+	if (eye_height)
+		dir[2] += target->viewheight;
+	dist = VectorLength(dir);
+	time = dist / bolt_speed;
+
+
+	VectorMA(target->s.origin, time - offset, target->velocity, vec);
+
+	if (eye_height)
+		vec[2] += target->viewheight;
+
+	if (aimdir)
+	{
+		VectorSubtract (vec, start, aimdir);
+		VectorNormalize (aimdir);
+	}
+	
+	if (aimpoint)
+	{
+		VectorCopy (vec, aimpoint);
+	}
+}
+
+
+qboolean below (edict_t *self, edict_t *other)
+{
+	vec3_t	vec;
+	float	dot;
+	vec3_t	down;
+	
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	VectorSet (down, 0, 0, -1);
+	dot = DotProduct (vec, down);
+	
+	if (dot > 0.95)  // 18 degree arc below
+		return true;
+	return false;
+}
+
+void drawbbox (edict_t *self)
+{
+	int	lines[4][3] = {
+		{1, 2, 4},
+		{1, 2, 7},
+		{1, 4, 5},
+		{2, 4, 7}
+	};
+
+	int starts[4] = {0, 3, 5, 6};
+
+	vec3_t pt[8];
+	int i, j, k;
+	vec3_t coords[2];
+	vec3_t newbox;
+	vec3_t f,r,u, dir;
+
+	VectorCopy (self->absmin, coords[0]);
+	VectorCopy (self->absmax, coords[1]);
+
+	for (i=0; i<=1; i++)
+	{
+		for (j=0; j<=1; j++)
+		{
+			for (k=0; k<=1; k++)
+			{
+				pt[4*i+2*j+k][0] = coords[i][0];
+				pt[4*i+2*j+k][1] = coords[j][1];
+				pt[4*i+2*j+k][2] = coords[k][2];
+			}
+		}
+	}
+	
+	for (i=0; i<= 3; i++)
+	{
+		for (j=0; j<= 2; j++)
+		{
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (pt[starts[i]]);
+			gi.WritePosition (pt[lines[i][j]]);
+			gi.multicast (pt[starts[i]], MULTICAST_ALL);	
+		}
+	}
+
+	vectoangles2 (self->s.angles, dir);
+	AngleVectors (dir, f, r, u);
+
+	VectorMA (self->s.origin, 50, f, newbox);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DEBUGTRAIL);
+	gi.WritePosition (self->s.origin);
+	gi.WritePosition (newbox);
+	gi.multicast (self->s.origin, MULTICAST_PVS);	
+	VectorClear (newbox);
+
+	VectorMA (self->s.origin, 50, r, newbox);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DEBUGTRAIL);
+	gi.WritePosition (self->s.origin);
+	gi.WritePosition (newbox);
+	gi.multicast (self->s.origin, MULTICAST_PVS);	
+	VectorClear (newbox);
+
+	VectorMA (self->s.origin, 50, u, newbox);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DEBUGTRAIL);
+	gi.WritePosition (self->s.origin);
+	gi.WritePosition (newbox);
+	gi.multicast (self->s.origin, MULTICAST_PVS);	
+	VectorClear (newbox);
+}
+
+//
+// New dodge code
+//
+void M_MonsterDodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+	float	r = qrandom();
+	float	height;
+	qboolean	ducker = false, dodger = false;
+
+	// this needs to be here since this can be called after the monster has "died"
+	if (self->health < 1)
+		return;
+
+	if ((self->monsterinfo.duck) && (self->monsterinfo.unduck))
+		ducker = true;
+	if ((self->monsterinfo.sidestep) && !(self->monsterinfo.aiflags & AI_STAND_GROUND))
+		dodger = true;
+
+	if ((!ducker) && (!dodger))
+		return;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//	{
+//		if (self->monsterinfo.aiflags & AI_DODGING)
+//			gi.dprintf ("dodging - ");
+//		if (self->monsterinfo.aiflags & AI_DUCKED)
+//			gi.dprintf ("ducked - ");
+//	}
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("timeout\n");
+		return;
+	}
+
+	// skill level determination..
+	if (r > (0.25*((skill->value)+1)))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("skillout\n");
+		return;
+	}
+
+	// stop charging, since we're going to dodge (somehow) instead
+//	soldier_stop_charge (self);
+
+	if (ducker)
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+
+		// FIXME, make smarter
+		// if we only duck, and ducking won't help or we're already ducking, do nothing
+		//
+		// need to add monsterinfo.abort_duck() and monsterinfo.next_duck_time
+
+		if ((!dodger) && ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED)))
+			return;
+	}
+	else
+		height = self->absmax[2];
+
+	if (dodger)
+	{
+		// if we're already dodging, just finish the sequence, i.e. don't do anything else
+		if (self->monsterinfo.aiflags & AI_DODGING)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("already dodging\n");
+			return;
+		}
+
+		// if we're ducking already, or the shot is at our knees
+		if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))
+		{
+			vec3_t right, diff;
+
+			AngleVectors (self->s.angles, NULL, right, NULL);
+			VectorSubtract (tr->endpos, self->s.origin, diff);
+
+			if (DotProduct (right, diff) < 0)
+			{
+				self->monsterinfo.lefty = 0;
+//				gi.dprintf ("left\n");
+			} else {
+				self->monsterinfo.lefty = 1;
+//				gi.dprintf ("right\n");
+			}
+	
+			// if we are currently ducked, unduck
+
+			if ((ducker) && (self->monsterinfo.aiflags & AI_DUCKED))
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("unducking - ");
+				self->monsterinfo.unduck(self);
+			}
+
+			self->monsterinfo.aiflags |= AI_DODGING;
+			self->monsterinfo.attack_state = AS_SLIDING;
+
+			// call the monster specific code here
+			self->monsterinfo.sidestep (self);
+			return;
+		}
+	}
+
+	if (ducker)
+	{
+		if (self->monsterinfo.next_duck_time > level.time)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("ducked too often, not ducking\n");
+			return;
+		}
+
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("ducking!\n");
+
+		monster_done_dodge (self);
+		// set this prematurely; it doesn't hurt, and prevents extra iterations
+		self->monsterinfo.aiflags |= AI_DUCKED;
+
+		self->monsterinfo.duck (self, eta);
+	}
+}
+
+void monster_duck_down (edict_t *self)
+{
+//	if (self->monsterinfo.aiflags & AI_DUCKED)
+//		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("duck down!\n");
+//	self->maxs[2] -= 32;
+	self->maxs[2] = self->monsterinfo.base_height - 32;
+	self->takedamage = DAMAGE_YES;
+	if (self->monsterinfo.duck_wait_time < level.time)
+		self->monsterinfo.duck_wait_time = level.time + 1;
+	gi.linkentity (self);
+}
+
+void monster_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.duck_wait_time)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void monster_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+//	self->maxs[2] += 32;
+	self->maxs[2] = self->monsterinfo.base_height;
+	self->takedamage = DAMAGE_AIM;
+	self->monsterinfo.next_duck_time = level.time + DUCK_INTERVAL;
+	gi.linkentity (self);
+}
+
+//=========================
+//=========================
+qboolean has_valid_enemy (edict_t *self)
+{
+	if (!self->enemy)
+		return false;
+
+	if (!self->enemy->inuse)
+		return false;
+
+	if (self->enemy->health < 1)
+		return false;
+
+	return true;
+}
+
+void TargetTesla (edict_t *self, edict_t *tesla)
+{
+	if ((!self) || (!tesla))
+		return;
+
+	// PMM - medic bails on healing things
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (self->enemy)
+			cleanupHealTarget(self->enemy);
+		self->monsterinfo.aiflags &= ~AI_MEDIC;
+	}
+
+	// store the player enemy in case we lose track of him.
+	if(self->enemy && self->enemy->client)
+		self->monsterinfo.last_player_enemy = self->enemy;
+
+	if(self->enemy != tesla)
+	{
+		self->oldenemy = self->enemy;
+		self->enemy = tesla;
+		if(self->monsterinfo.attack)
+		{
+			if (self->health <= 0)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("bad tesla attack avoided!\n");
+				return;
+			}
+			self->monsterinfo.attack(self);
+		}
+		else
+		{
+			FoundTarget(self);
+		}
+	}
+}
+
+// this returns a randomly selected coop player who is visible to self
+// returns NULL if bad
+
+edict_t * PickCoopTarget (edict_t *self)
+{
+	// no more than 4 players in coop, so..
+	edict_t *targets[4];
+	int		num_targets = 0, targetID;
+	edict_t *ent;
+	int		player;
+
+	// if we're not in coop, this is a noop
+	if (!coop || !coop->value)
+		return NULL;
+
+	memset (targets, 0, 4*sizeof(edict_t *));
+
+	for (player = 1; player <= game.maxclients; player++)
+	{
+		ent = &g_edicts[player];
+		if (!ent->inuse)
+			continue;
+		if (!ent->client)
+			continue;
+		if (visible(self, ent))
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("%s: found coop player %s - ", self->classname, ent->client->pers.netname);
+			targets[num_targets++] = ent;
+		}
+	}
+
+/*
+	ent = g_edicts+1; // skip the worldspawn
+	// cycle through players
+	while (ent)
+	{
+		if ((ent->client) && (ent->inuse))
+		{
+			if (visible(self, ent))
+			{
+				if ((g_showlogic) && (g_showlogic->value))
+					gi.dprintf ("%s: found coop player %s - ", self->classname, ent->client->pers.netname);
+				targets[num_targets++] = ent;
+			}
+			ent++;
+		}
+		else
+			ent = NULL;
+	}
+*/
+
+	if (!num_targets)
+		return NULL;
+
+	// get a number from 0 to (num_targets-1)
+	targetID = (qrandom() * (float)num_targets);
+	
+	// just in case we got a 1.0 from random
+	if (targetID == num_targets)
+		targetID--;
+
+//	if (g_showlogic && g_showlogic->value)
+//		gi.dprintf ("using player %s\n", targets[targetID]->client->pers.netname);
+	return targets[targetID];
+}
+
+// only meant to be used in coop
+int CountPlayers (void)
+{
+	edict_t *ent;
+	int		count = 0;
+	int		player;
+
+	// if we're not in coop, this is a noop
+	if (!coop || !coop->value)
+		return 1;
+
+	for (player = 1; player <= game.maxclients; player++)
+	{
+		ent = &g_edicts[player];
+		if (!ent->inuse)
+			continue;
+		if (!ent->client)
+			continue;
+		count++;
+	}
+/*
+	ent = g_edicts+1; // skip the worldspawn
+	while (ent)
+	{
+		if ((ent->client) && (ent->inuse))
+		{
+			ent++;
+			count++;
+		}
+		else
+			ent = NULL;
+	}
+*/
+	return count;
+}
+
+//*******************
+// JUMPING AIDS
+//*******************
+
+void monster_jump_start (edict_t *self)
+{
+	self->timestamp = level.time;
+}
+
+qboolean monster_jump_finished (edict_t *self)
+{
+	if ((level.time - self->timestamp) > 3)
+	{
+//		if (g_showlogic && g_showlogic->value)
+//		{
+//			gi.dprintf("%s jump timed out!\n", self->classname);
+//		}
+		return true;
+	}
+	return false;
+}
+
--- /dev/null
+++ b/rogue/g_newdm.c
@@ -1,0 +1,384 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+dm_game_rt	DMGame;
+
+// ****************************
+// General DM Stuff
+// ****************************
+
+void InitGameRules(void)
+{
+	int		gameNum;
+
+	// clear out the game rule structure before we start
+	memset(&DMGame, 0, sizeof(dm_game_rt));
+
+	if(gamerules && gamerules->value)
+	{
+		gameNum = gamerules->value;
+		switch(gameNum)
+		{
+			case RDM_TAG:
+				DMGame.GameInit = Tag_GameInit;
+				DMGame.PostInitSetup = Tag_PostInitSetup;
+				DMGame.PlayerDeath = Tag_PlayerDeath;
+				DMGame.Score = Tag_Score;
+				DMGame.PlayerEffects = Tag_PlayerEffects;
+				DMGame.DogTag = Tag_DogTag;
+				DMGame.PlayerDisconnect = Tag_PlayerDisconnect;
+				DMGame.ChangeDamage = Tag_ChangeDamage;
+				break;
+/*
+			case RDM_DEATHBALL:
+				DMGame.GameInit = DBall_GameInit;
+				DMGame.ChangeKnockback = DBall_ChangeKnockback;
+				DMGame.ChangeDamage = DBall_ChangeDamage;
+				DMGame.ClientBegin = DBall_ClientBegin;
+				DMGame.SelectSpawnPoint = DBall_SelectSpawnPoint;
+				DMGame.PostInitSetup = DBall_PostInitSetup;
+				DMGame.CheckDMRules = DBall_CheckDMRules;
+				break;
+*/
+			// reset gamerules if it's not a valid number
+			default:
+				gamerules->value = 0;
+				break;
+		}
+	}
+
+	// if we're set up to play, initialize the game as needed.
+	if(DMGame.GameInit)
+		DMGame.GameInit();
+}
+
+//=================
+//=================
+#define IT_TYPE_MASK	(IT_WEAPON|IT_AMMO|IT_POWERUP|IT_ARMOR|IT_KEY)
+
+extern void ED_CallSpawn (edict_t *ent);
+extern qboolean Pickup_Health (edict_t *ent, edict_t *other);
+extern qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other);
+extern qboolean Pickup_Armor (edict_t *ent, edict_t *other);
+extern qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other);
+
+char *FindSubstituteItem (edict_t *ent)
+{
+	int		i;
+	int		itflags, myflags;
+	float	rnd;
+	int		count;
+	int		pick;
+	gitem_t	*it;
+
+	// there are only two classes of power armor, and we don't want
+	// to give out power screens. therefore, power shields should
+	// remain power shields. (powerscreens shouldn't be there at all...)
+	if (ent->item->pickup == Pickup_PowerArmor)
+		return NULL;
+
+	// health is special case
+	if ((ent->item->pickup == Pickup_Health) ||	(ent->item->pickup == Pickup_Adrenaline))
+	{
+		// health pellets stay health pellets
+		if(!strcmp(ent->classname, "item_health_small"))
+			return NULL;
+
+		rnd = qrandom();
+		if(rnd < 0.6)
+			return "item_health";
+		else if(rnd < 0.9)
+			return "item_health_large";
+		else if(rnd < 0.99)
+			return "item_adrenaline";
+		else
+			return "item_health_mega";
+	}
+	// armor is also special case
+	else if(ent->item->pickup == Pickup_Armor)
+	{
+		// armor shards stay armor shards
+		if (ent->item->tag == ARMOR_SHARD)
+			return NULL;
+
+		rnd = qrandom();
+		if(rnd < 0.6)
+			return "item_armor_jacket";
+		else if(rnd < 0.9)
+			return "item_armor_combat";
+		else
+			return "item_armor_body";
+	}
+
+
+	// we want to stay within the item class
+	myflags = ent->item->flags & IT_TYPE_MASK;
+	if ((myflags & IT_AMMO) && (myflags & IT_WEAPON))
+			myflags = IT_AMMO;
+
+	count = 0;
+
+	// first pass, count the matching items
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		itflags = it->flags;
+		
+		if (!itflags || (itflags & IT_NOT_GIVEABLE))
+			continue;
+
+		// prox,grenades,etc should count as ammo.
+		if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
+			itflags = IT_AMMO;
+
+		// don't respawn spheres if they're dmflag disabled.
+		if ( (int)dmflags->value & DF_NO_SPHERES )
+		{
+			if (!strcmp (ent->classname, "item_sphere_vengeance") ||
+				!strcmp (ent->classname, "item_sphere_hunter") ||
+				!strcmp (ent->classname, "item_spehre_defender"))
+			{
+				continue;
+			}
+		}
+
+		if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") )
+			continue;
+
+		if ( ((int)dmflags->value & DF_NO_MINES) && 
+				(!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
+			continue;
+
+		if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
+			count++;
+	}
+
+	if(!count)
+		return NULL;
+
+	pick = ceil(qrandom() * count);
+	count = 0;
+
+	// second pass, pick one.
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		itflags = it->flags;
+		
+		if (!itflags || (itflags & IT_NOT_GIVEABLE))
+			continue;
+
+		// prox,grenades,etc should count as ammo.
+		if ((itflags & IT_AMMO) && (itflags & IT_WEAPON))
+			itflags = IT_AMMO;
+
+		if ( ((int)dmflags->value & DF_NO_NUKES) && !strcmp(ent->classname, "ammo_nuke") )
+			continue;
+
+		if ( ((int)dmflags->value & DF_NO_MINES) && 
+				(!strcmp(ent->classname, "ammo_prox") || !strcmp(ent->classname, "ammo_tesla")))
+			continue;
+
+		if ((itflags & IT_TYPE_MASK) == (myflags & IT_TYPE_MASK))
+		{
+			count++;
+			if(pick == count)
+				return it->classname;
+		}
+	}
+
+	return NULL;
+}
+
+//=================
+//=================
+edict_t *DoRandomRespawn (edict_t *ent)
+{
+	edict_t *newEnt;
+	char	*classname;
+	
+	classname = FindSubstituteItem (ent);
+	if (classname == NULL)
+		return NULL;
+
+	gi.unlinkentity (ent);
+
+	newEnt = G_Spawn();
+	newEnt->classname = classname;
+	VectorCopy (ent->s.origin, newEnt->s.origin);
+	VectorCopy (ent->s.old_origin, newEnt->s.old_origin);
+	VectorCopy (ent->mins, newEnt->mins);
+	VectorCopy (ent->maxs, newEnt->maxs);
+	
+	VectorSet (newEnt->gravityVector, 0, 0, -1);
+
+	ED_CallSpawn (newEnt);
+
+	newEnt->s.renderfx |= RF_IR_VISIBLE;
+
+	return newEnt;
+}
+
+//=================
+//=================
+void PrecacheForRandomRespawn (void)
+{
+	gitem_t	*it;
+	int		i;
+	int		itflags;
+
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		itflags = it->flags;
+		
+		if (!itflags || (itflags & IT_NOT_GIVEABLE))
+			continue;
+
+		PrecacheItem(it);
+	}
+}
+
+// ***************************
+//  DOPPLEGANGER
+// ***************************
+
+extern edict_t *Sphere_Spawn (edict_t *owner, int spawnflags);
+
+void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir);
+void doppleganger_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+void doppleganger_die (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	edict_t *sphere;
+	float	dist;
+	vec3_t	dir;
+
+	if((self->enemy) && (self->enemy != self->teammaster))
+	{
+		VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+		dist = VectorLength(dir);
+
+		if(dist > 768)
+		{
+			sphere = Sphere_Spawn (self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER);
+			sphere->pain(sphere, attacker, 0, 0);
+		}
+		else //if(dist > 256)
+		{
+			sphere = Sphere_Spawn (self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER);
+			sphere->pain(sphere, attacker, 0, 0);
+		}
+//		else
+//		{
+//			T_RadiusClassDamage (self, self->teammaster, 175, "doppleganger", 384, MOD_DOPPLE_EXPLODE);
+//		}
+	}
+
+	if(self->teamchain)
+		BecomeExplosion1(self->teamchain);
+	BecomeExplosion1(self);
+}
+
+void doppleganger_pain (edict_t *self, edict_t *other, float, int)
+{
+	self->enemy = other;
+}
+
+void doppleganger_timeout (edict_t *self)
+{
+//	T_RadiusClassDamage (self, self->teammaster, 140, "doppleganger", 256, MOD_DOPPLE_EXPLODE);
+
+	if(self->teamchain)
+		BecomeExplosion1(self->teamchain);
+	BecomeExplosion1(self);
+}
+
+void body_think (edict_t *self)
+{
+	float r;
+
+	if(abs(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2)
+	{
+		if(self->timestamp < level.time)
+		{
+			r = qrandom();
+			if(r < 0.10)
+			{
+				self->ideal_yaw = qrandom() * 350.0;
+				self->timestamp = level.time + 1;
+			}
+		}
+	}
+	else
+		M_ChangeYaw(self);
+
+	self->s.frame ++;
+	if (self->s.frame > FRAME_stand40)
+		self->s.frame = FRAME_stand01;
+
+	self->nextthink = level.time + 0.1;
+}
+
+void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir)
+{
+	edict_t		*base;
+	edict_t		*body;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	int			number;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	base = G_Spawn();
+	VectorCopy (start, base->s.origin);
+	VectorCopy (dir, base->s.angles);
+	VectorClear (base->velocity);
+	VectorClear (base->avelocity);
+	base->movetype = MOVETYPE_TOSS;
+	base->solid = SOLID_BBOX;
+	base->s.renderfx |= RF_IR_VISIBLE;
+	base->s.angles[PITCH]=0;
+	VectorSet (base->mins, -16, -16, -24);
+	VectorSet (base->maxs, 16, 16, 32);
+//	base->s.modelindex = gi.modelindex ("models/objects/dopplebase/tris.md2");
+	base->s.modelindex = 0;
+	base->teammaster = ent;
+	base->svflags |= SVF_DAMAGEABLE;
+	base->takedamage = DAMAGE_AIM;
+	base->health = 30;
+	base->pain = doppleganger_pain;
+	base->die = doppleganger_die;
+
+	// FIXME - remove with style
+	base->nextthink = level.time + 30;
+	base->think = doppleganger_timeout;
+
+	base->classname = "doppleganger";
+
+	gi.linkentity (base);
+
+	body = G_Spawn();
+	number = body->s.number;
+	body->s = ent->s;
+	body->s.sound = 0;
+	body->s.event = 0;
+//	body->s.modelindex2 = 0;		// no attached items (CTF flag, etc)
+	body->s.number = number;
+	body->yaw_speed = 30;
+	body->ideal_yaw = 0;
+	VectorCopy (start, body->s.origin);
+	body->s.origin[2] += 8;
+	body->think = body_think;
+	body->nextthink = level.time + FRAMETIME;
+	gi.linkentity (body);
+
+	base->teamchain = body;
+	body->teammaster = base;
+}
+
--- /dev/null
+++ b/rogue/g_newfnc.c
@@ -1,0 +1,351 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+//void plat_CalcMove (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
+void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
+
+void fd_secret_move1(edict_t *self);
+void fd_secret_move2(edict_t *self);
+void fd_secret_move3(edict_t *self);
+void fd_secret_move4(edict_t *self);
+void fd_secret_move5(edict_t *self);
+void fd_secret_move6(edict_t *self);
+void fd_secret_done(edict_t *self);
+
+/*
+=============================================================================
+
+SECRET DOORS
+
+=============================================================================
+*/
+
+#define SEC_OPEN_ONCE		1          // stays open
+#define SEC_1ST_LEFT		2          // 1st move is left of arrow
+#define SEC_1ST_DOWN		4          // 1st move is down from arrow
+#define SEC_NO_SHOOT		8          // only opened by trigger
+#define SEC_YES_SHOOT		16		   // shootable even if targeted
+#define SEC_MOVE_RIGHT		32
+#define SEC_MOVE_FORWARD	64
+
+void fd_secret_use (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t *ent;
+
+//	gi.dprintf("fd_secret_use\n");
+	if (self->flags & FL_TEAMSLAVE)
+		return;
+
+	// trigger all paired doors
+	for (ent = self ; ent ; ent = ent->teamchain)
+		Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1);
+
+}
+
+void fd_secret_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+//	gi.dprintf("fd_secret_killed\n");
+	self->health = self->max_health;
+	self->takedamage = DAMAGE_NO;
+
+	if (self->flags & FL_TEAMSLAVE && self->teammaster && self->teammaster->takedamage != DAMAGE_NO)
+		fd_secret_killed (self->teammaster, inflictor, attacker, damage, point);
+	else
+		fd_secret_use (self, inflictor, attacker);
+}
+
+// Wait after first movement...
+void fd_secret_move1(edict_t *self) 
+{
+//	gi.dprintf("fd_secret_move1\n");
+	self->nextthink = level.time + 1.0;
+	self->think = fd_secret_move2;
+}
+
+// Start moving sideways w/sound...
+void fd_secret_move2(edict_t *self)
+{
+//	gi.dprintf("fd_secret_move2\n");
+	Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3);
+}
+
+// Wait here until time to go back...
+void fd_secret_move3(edict_t *self)
+{
+//	gi.dprintf("fd_secret_move3\n");
+	if (!(self->spawnflags & SEC_OPEN_ONCE))
+	{
+		self->nextthink = level.time + self->wait;
+		self->think = fd_secret_move4;
+	}
+}
+
+// Move backward...
+void fd_secret_move4(edict_t *self)
+{
+//	gi.dprintf("fd_secret_move4\n");
+	Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5);          
+}
+
+// Wait 1 second...
+void fd_secret_move5(edict_t *self)
+{
+//	gi.dprintf("fd_secret_move5\n");
+	self->nextthink = level.time + 1.0;
+	self->think = fd_secret_move6;
+}
+
+void fd_secret_move6(edict_t *self)
+{
+//	gi.dprintf("fd_secret_move6\n");
+	Move_Calc(self, self->move_origin, fd_secret_done);
+}
+
+void fd_secret_done(edict_t *self)
+{
+//	gi.dprintf("fd_secret_done\n");
+	if (!self->targetname || self->spawnflags & SEC_YES_SHOOT)
+	{
+		self->health = 1;
+		self->takedamage = DAMAGE_YES;
+		self->die = fd_secret_killed;   
+	}
+}
+
+void secret_blocked(edict_t *self, edict_t *other)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 0, 0, MOD_CRUSH);
+
+//	if (time < self->attack_finished)
+//		return;
+//	self->attack_finished = time + 0.5;
+//	T_Damage (other, self, self, self->dmg);
+}
+
+/*
+================
+secret_touch
+
+Prints messages
+================
+*/
+void secret_touch(edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->health <= 0)
+		return;
+
+	if (!(other->client))
+		return;
+
+	if (self->monsterinfo.attack_finished > level.time)
+		return;
+
+	self->monsterinfo.attack_finished = level.time + 2;
+	
+	if (self->message)
+	{
+		gi.centerprintf (other, self->message);
+//		fixme - put this sound back??
+//		gi.sound (other, CHAN_BODY, "misc/talk.wav", 1, ATTN_NORM);
+	}
+}
+
+
+/*QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward
+Basic secret door. Slides back, then to the left. Angle determines direction.
+
+FLAGS:
+open_once = not implemented yet
+1st_left = 1st move is left/right of arrow
+1st_down = 1st move is forwards/backwards
+no_shoot = not implemented yet
+always_shoot = even if targeted, keep shootable
+reverse_left = the sideways move will be to right of arrow
+reverse_back = the to/fro move will be forward
+
+VALUES:
+wait = # of seconds before coming back (5 default)
+dmg  = damage to inflict when blocked (2 default)
+
+*/
+
+void SP_func_door_secret2 (edict_t *ent)
+{
+	vec3_t	forward,right,up;
+	float	lrSize = 0.0, fbSize = 0.0;
+
+	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+		
+	AngleVectors(ent->s.angles, forward, right, up);
+	VectorCopy(ent->s.origin, ent->move_origin);
+	VectorCopy(ent->s.angles, ent->move_angles);
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	if(ent->move_angles[1] == 0 || ent->move_angles[1] == 180)
+	{
+		lrSize = ent->size[1];
+		fbSize = ent->size[0];
+	}		
+	else if(ent->move_angles[1] == 90 || ent->move_angles[1] == 270)
+	{
+		lrSize = ent->size[0];
+		fbSize = ent->size[1];
+	}		
+	else
+	{
+		gi.dprintf("Secret door not at 0,90,180,270!\n");
+	}
+
+	if(ent->spawnflags & SEC_MOVE_FORWARD)
+		VectorScale(forward, fbSize, forward);
+	else
+	{
+		VectorScale(forward, fbSize * -1 , forward);
+	}
+
+	if(ent->spawnflags & SEC_MOVE_RIGHT)
+		VectorScale(right, lrSize, right);
+	else
+	{
+		VectorScale(right, lrSize * -1, right);
+	}
+
+	if(ent->spawnflags & SEC_1ST_DOWN)
+	{
+		VectorAdd(ent->s.origin, forward, ent->moveinfo.start_origin);
+		VectorAdd(ent->moveinfo.start_origin, right, ent->moveinfo.end_origin);
+	}
+	else
+	{
+		VectorAdd(ent->s.origin, right, ent->moveinfo.start_origin);
+		VectorAdd(ent->moveinfo.start_origin, forward, ent->moveinfo.end_origin);
+	}
+
+	ent->touch = secret_touch;
+	ent->blocked = secret_blocked;
+	ent->use = fd_secret_use;
+	ent->moveinfo.speed = 50;
+	ent->moveinfo.accel = 50;
+	ent->moveinfo.decel = 50;
+
+	if (!ent->targetname || ent->spawnflags & SEC_YES_SHOOT)
+	{
+		ent->health = 1;
+		ent->max_health = ent->health;
+		ent->takedamage = DAMAGE_YES;
+		ent->die = fd_secret_killed;
+	}
+	if (!ent->wait)
+		ent->wait = 5;          // 5 seconds before closing
+
+	gi.linkentity(ent);
+}
+
+// ==================================================
+
+#define FWALL_START_ON		1
+
+void force_wall_think(edict_t *self)
+{
+	if(!self->wait)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_FORCEWALL);
+		gi.WritePosition (self->pos1);
+		gi.WritePosition (self->pos2);
+		gi.WriteByte  (self->style);
+		gi.multicast (self->offset, MULTICAST_PVS);
+	}
+
+	self->think = force_wall_think;
+	self->nextthink = level.time + 0.1;
+}
+
+void force_wall_use (edict_t *self, edict_t *, edict_t *)
+{
+	if(!self->wait)
+	{
+		self->wait = 1;
+		self->think = NULL;
+		self->nextthink = 0;
+		self->solid = SOLID_NOT;
+		gi.linkentity( self );
+	}
+	else
+	{
+		self->wait = 0;
+		self->think = force_wall_think;
+		self->nextthink = level.time + 0.1;
+		self->solid = SOLID_BSP;
+		KillBox(self);		// Is this appropriate?
+		gi.linkentity (self);
+	}
+}
+
+/*QUAKED func_force_wall (1 0 1) ? start_on
+A vertical particle force wall. Turns on and solid when triggered.
+If someone is in the force wall when it turns on, they're telefragged.
+
+start_on - forcewall begins activated. triggering will turn it off.
+style - color of particles to use.
+	208: green, 240: red, 241: blue, 224: orange
+*/
+void SP_func_force_wall(edict_t *ent)
+{
+	gi.setmodel (ent, ent->model);
+
+	ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2;
+	ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2;
+	ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2;
+
+	ent->pos1[2] = ent->absmax[2];
+	ent->pos2[2] = ent->absmax[2];
+	if(ent->size[0] > ent->size[1])
+	{
+		ent->pos1[0] = ent->absmin[0];
+		ent->pos2[0] = ent->absmax[0];
+		ent->pos1[1] = ent->offset[1];
+		ent->pos2[1] = ent->offset[1];
+	}
+	else
+	{
+		ent->pos1[0] = ent->offset[0];
+		ent->pos2[0] = ent->offset[0];
+		ent->pos1[1] = ent->absmin[1];
+		ent->pos2[1] = ent->absmax[1];
+	}
+	
+	if(!ent->style)
+		ent->style = 208;
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->wait = 1;
+
+	if(ent->spawnflags & FWALL_START_ON)
+	{
+		ent->solid = SOLID_BSP;
+		ent->think = force_wall_think;
+		ent->nextthink = level.time + 0.1;
+	}
+	else
+		ent->solid = SOLID_NOT;
+
+	ent->use = force_wall_use;
+
+	ent->svflags = SVF_NOCLIENT;
+	
+	gi.linkentity(ent);
+}
--- /dev/null
+++ b/rogue/g_newtarg.c
@@ -1,0 +1,356 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+//==========================================================
+
+/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8)
+Creates a steam effect (particles w/ velocity in a line).
+
+  speed = velocity of particles (default 50)
+  count = number of particles (default 32)
+  sounds = color of particles (default 8 for steam)
+     the color range is from this color to this color + 6
+  wait = seconds to run before stopping (overrides default
+     value derived from func_timer)
+
+  best way to use this is to tie it to a func_timer that "pokes"
+  it every second (or however long you set the wait time, above)
+
+  note that the width of the base is proportional to the speed
+  good colors to use:
+  6-9 - varying whites (darker to brighter)
+  224 - sparks
+  176 - blue water
+  80  - brown water
+  208 - slime
+  232 - blood
+*/
+
+void use_target_steam (edict_t *self, edict_t *other, edict_t *)
+{
+	// FIXME - this needs to be a global
+	static int	nextid;
+	vec3_t		point;
+
+	if (nextid > 20000)
+		nextid = nextid %20000;
+
+	nextid++;
+	
+	// automagically set wait from func_timer unless they set it already, or
+	// default to 1000 if not called by a func_timer (eek!)
+	if (!self->wait)
+		if (other)
+			self->wait = other->wait * 1000;
+		else
+			self->wait = 1000;
+
+	if (self->enemy)
+	{
+		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
+		VectorSubtract (point, self->s.origin, self->movedir);
+		VectorNormalize (self->movedir);
+	}
+
+	VectorMA (self->s.origin, self->plat2flags*0.5, self->movedir, point);
+	if (self->wait > 100)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_STEAM);
+		gi.WriteShort (nextid);
+		gi.WriteByte (self->count);
+		gi.WritePosition (self->s.origin);
+		gi.WriteDir (self->movedir);
+		gi.WriteByte (self->sounds&0xff);
+		gi.WriteShort ( (short int)(self->plat2flags) );
+		gi.WriteLong ( (int)(self->wait) );
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+	else
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_STEAM);
+		gi.WriteShort ((short int)-1);
+		gi.WriteByte (self->count);
+		gi.WritePosition (self->s.origin);
+		gi.WriteDir (self->movedir);
+		gi.WriteByte (self->sounds&0xff);
+		gi.WriteShort ( (short int)(self->plat2flags) );
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+}
+
+void target_steam_start (edict_t *self)
+{
+	edict_t	*ent;
+
+	self->use = use_target_steam;
+
+	if (self->target)
+	{
+		ent = G_Find (NULL, FOFS(targetname), self->target);
+		if (!ent)
+			gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
+		self->enemy = ent;
+	}
+	else
+	{
+		G_SetMovedir (self->s.angles, self->movedir);
+	}
+
+	if (!self->count)
+		self->count = 32;
+	if (!self->plat2flags)
+		self->plat2flags = 75;
+	if (!self->sounds)
+		self->sounds = 8;
+	if (self->wait)
+		self->wait *= 1000;  // we want it in milliseconds, not seconds
+
+	// paranoia is good
+	self->sounds &= 0xff;
+	self->count &= 0xff;
+
+	self->svflags = SVF_NOCLIENT;
+
+	gi.linkentity (self);
+}
+
+void SP_target_steam (edict_t *self)
+{
+	self->plat2flags = self->speed;
+
+	if (self->target)
+	{
+		self->think = target_steam_start;
+		self->nextthink = level.time + 1;
+	}
+	else
+		target_steam_start (self);
+}
+
+
+//==========================================================
+// target_anger
+//==========================================================
+
+void target_anger_use (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t		*target;
+	edict_t		*t;
+
+	t = NULL;
+	target = G_Find (t, FOFS(targetname), self->killtarget);
+
+	if (target && self->target)
+	{
+		// Make whatever a "good guy" so the monster will try to kill it!
+		target->monsterinfo.aiflags |= AI_GOOD_GUY;
+		target->svflags |= SVF_MONSTER;
+		target->health = 300;
+
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), self->target)))
+		{
+			if (t == self)
+			{
+				gi.dprintf ("WARNING: entity used itself.\n");
+			}
+			else
+			{
+				if (t->use)
+				{
+					if (t->health < 0)
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("target_anger with dead monster!\n");
+						return;
+					}
+					t->enemy = target;
+					t->monsterinfo.aiflags |= AI_TARGET_ANGER;
+					FoundTarget (t);
+				}
+			}
+			if (!self->inuse)
+			{
+				gi.dprintf("entity was removed while using targets\n");
+				return;
+			}
+		}
+	}
+
+}
+
+/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8)
+This trigger will cause an entity to be angry at another entity when a player touches it. Target the
+entity you want to anger, and killtarget the entity you want it to be angry at.
+
+target - entity to piss off
+killtarget - entity to be pissed off at
+*/
+void SP_target_anger (edict_t *self)
+{	
+	if (!self->target)
+	{
+		gi.dprintf("target_anger without target!\n");
+		G_FreeEdict (self);
+		return;
+	}
+	if (!self->killtarget)
+	{
+		gi.dprintf("target_anger without killtarget!\n");
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->use = target_anger_use;
+	self->svflags = SVF_NOCLIENT;
+}
+
+// ================
+// target_spawn
+// ================
+/*
+extern edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname);
+
+void target_spawn_use (edict_t *self, edict_t *other, edict_t *activator)
+{
+	edict_t		*newEnt;
+
+	newEnt = CreateMonster (self->s.origin, self->s.angles, "monster_infantry");
+	if(newEnt)
+		newEnt->enemy = other;
+}
+*/
+
+/*Q U AKED target_spawn (1 0 0) (-32 -32 -24) (32 32 72) 
+*/
+/*
+void SP_target_spawn (edict_t *self)
+{
+	self->use = target_spawn_use;
+	self->svflags = SVF_NOCLIENT;
+}
+*/
+
+// ***********************************
+// target_killplayers
+// ***********************************
+
+void target_killplayers_use (edict_t *self, edict_t *, edict_t *)
+{
+	int		i;
+	edict_t	*ent, *player;
+
+	// kill the players
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		player = &g_edicts[1+i];
+		if (!player->inuse)
+			continue;
+
+		// nail it
+		T_Damage (player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+	}
+
+	// kill any visible monsters
+	for (ent = g_edicts; ent < &g_edicts[globals.num_edicts] ; ent++)
+	{
+		if (!ent->inuse)
+			continue;
+		if (ent->health < 1)
+			continue;
+		if (!ent->takedamage)
+			continue;
+		
+		for(i=0;i<game.maxclients ; i++)
+		{
+			player = &g_edicts[1+i];
+			if(!player->inuse)
+				continue;
+			
+			if(visible(player, ent))
+			{
+				T_Damage (ent, self, self, vec3_origin, ent->s.origin, vec3_origin, 
+							ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+				break;
+			}
+		}
+	}
+
+}
+
+/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8)
+When triggered, this will kill all the players on the map.
+*/
+void SP_target_killplayers (edict_t *self)
+{
+	self->use = target_killplayers_use;
+	self->svflags = SVF_NOCLIENT;
+}
+
+/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24) 
+Pulsing black light with sphere in the center
+*/
+void blacklight_think (edict_t *self)
+{
+	self->s.angles[0] = rand()%360;
+	self->s.angles[1] = rand()%360;
+	self->s.angles[2] = rand()%360;
+	self->nextthink = level.time + 0.1;
+}
+
+void SP_target_blacklight(edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	VectorClear (ent->mins);
+	VectorClear (ent->maxs);
+
+	ent->s.effects |= (EF_TRACKERTRAIL|EF_TRACKER);
+	ent->think = blacklight_think;
+	ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
+	ent->s.frame = 1;
+	ent->nextthink = level.time + 0.1;
+	gi.linkentity (ent);
+}
+
+/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24) 
+Translucent pulsing orb with speckles
+*/
+void orb_think (edict_t *self)
+{
+	self->s.angles[0] = rand()%360;
+	self->s.angles[1] = rand()%360;
+	self->s.angles[2] = rand()%360;
+//	self->s.effects |= (EF_TRACKERTRAIL|EF_DOUBLE);
+	self->nextthink = level.time + 0.1;
+}
+
+void SP_target_orb(edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	VectorClear (ent->mins);
+	VectorClear (ent->maxs);
+
+//	ent->s.effects |= EF_TRACKERTRAIL;
+	ent->think = orb_think;
+	ent->nextthink = level.time + 0.1;
+	ent->s.modelindex = gi.modelindex ("models/items/spawngro2/tris.md2");
+	ent->s.frame = 2;
+	ent->s.effects |= EF_SPHERETRANS;
+	gi.linkentity (ent);
+}
+
--- /dev/null
+++ b/rogue/g_newtrig.c
@@ -1,0 +1,176 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define TELEPORT_PLAYER_ONLY	1
+#define TELEPORT_SILENT			2
+#define TELEPORT_CTF_ONLY		4
+#define TELEPORT_START_ON		8
+
+extern void TeleportEffect (vec3_t origin);
+
+/*QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32)
+Destination marker for a teleporter.
+*/
+void SP_info_teleport_destination (edict_t *)
+{
+}
+
+/*QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on
+Any object touching this will be transported to the corresponding 
+info_teleport_destination entity. You must set the "target" field, 
+and create an object with a "targetname" field that matches.
+
+If the trigger_teleport has a targetname, it will only teleport 
+entities when it has been fired.
+
+player_only: only players are teleported
+silent: <not used right now>
+ctf_only: <not used right now>
+start_on: when trigger has targetname, start active, deactivate when used.
+*/
+void trigger_teleport_touch(edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t *dest;
+	int		i;
+
+	if(/*(self->spawnflags & TELEPORT_PLAYER_ONLY) &&*/ !(other->client))
+		return;
+
+	if(self->delay)
+		return;
+
+	dest = G_Find (NULL, FOFS(targetname), self->target);
+	if(!dest)
+	{
+		gi.dprintf("Teleport Destination not found!\n");
+		return;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_TELEPORT_EFFECT);
+	gi.WritePosition (other->s.origin);
+	gi.multicast (other->s.origin, MULTICAST_PVS);
+
+	// unlink to make sure it can't possibly interfere with KillBox
+	gi.unlinkentity (other);
+
+	VectorCopy (dest->s.origin, other->s.origin);
+	VectorCopy (dest->s.origin, other->s.old_origin);
+	other->s.origin[2] += 10;
+
+	// clear the velocity and hold them in place briefly
+	VectorClear (other->velocity);
+	if(other->client)
+	{
+		other->client->ps.pmove.pm_time = 160>>3;		// hold time
+		other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+		// draw the teleport splash at source and on the player
+//		self->s.event = EV_PLAYER_TELEPORT;
+		other->s.event = EV_PLAYER_TELEPORT;
+
+		// set angles
+		for (i=0 ; i<3 ; i++)
+			other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+
+		VectorClear (other->client->ps.viewangles);
+		VectorClear (other->client->v_angle);
+	}
+
+
+	VectorClear (other->s.angles);
+
+	// kill anything at the destination
+	KillBox (other);
+
+	gi.linkentity (other);
+}
+
+void trigger_teleport_use (edict_t *self, edict_t *, edict_t *)
+{
+	if(self->delay)
+		self->delay = 0;
+	else
+		self->delay = 1;
+}
+
+void SP_trigger_teleport(edict_t *self)
+{
+	if (!self->wait)
+		self->wait = 0.2;
+
+	self->delay = 0;
+		
+	if (self->targetname)
+	{
+		self->use = trigger_teleport_use;
+		if(!(self->spawnflags & TELEPORT_START_ON))
+			self->delay = 1;
+	}
+
+	self->touch = trigger_teleport_touch;
+
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+//	self->flags |= FL_NOCLIENT;
+
+	if (!VectorCompare(self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	gi.setmodel (self, self->model);
+	gi.linkentity (self);
+}
+
+// ***************************
+// TRIGGER_DISGUISE
+// ***************************
+
+/*QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE
+Anything passing through this trigger when it is active will
+be marked as disguised.
+
+TOGGLE - field is turned off and on when used.
+START_ON - field is active when spawned.
+REMOVE - field removes the disguise
+*/
+
+void trigger_disguise_touch(edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->client)
+	{
+		if(self->spawnflags & 4)
+			other->flags &= ~FL_DISGUISED;
+		else
+			other->flags |= FL_DISGUISED;
+	}
+}
+
+void trigger_disguise_use (edict_t *self, edict_t *, edict_t *)
+{
+	if(self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+
+	gi.linkentity(self);
+}
+
+void SP_trigger_disguise (edict_t *self)
+{
+	if(self->spawnflags & 2)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+
+	self->touch = trigger_disguise_touch;
+	self->use = trigger_disguise_use;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags = SVF_NOCLIENT;
+
+	gi.setmodel (self, self->model);
+	gi.linkentity(self);
+
+}
--- /dev/null
+++ b/rogue/g_newweap.c
@@ -1,0 +1,2284 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define INCLUDE_ETF_RIFLE		1
+#define INCLUDE_PROX			1
+//#define INCLUDE_FLAMETHROWER	1
+//#define INCLUDE_INCENDIARY		1
+#define INCLUDE_NUKE			1
+#define INCLUDE_MELEE			1
+#define INCLUDE_TESLA			1
+#define INCLUDE_BEAMS			1
+
+extern void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed);
+extern void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+extern void droptofloor (edict_t *ent);
+extern void Grenade_Explode (edict_t *ent);
+
+extern void drawbbox (edict_t *ent);
+
+#ifdef INCLUDE_ETF_RIFLE
+/*
+========================
+fire_flechette
+========================
+*/
+void flechette_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	vec3_t		dir;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+//gi.dprintf("t_damage %s\n", other->classname);
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
+			self->dmg, self->dmg_radius, DAMAGE_NO_REG_ARMOR, MOD_ETF_RIFLE);
+	}
+	else
+	{
+		if(!plane)
+			VectorClear (dir);
+		else
+			VectorScale (plane->normal, 256, dir);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_FLECHETTE);
+		gi.WritePosition (self->s.origin);
+		gi.WriteDir (dir);
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+
+//		T_RadiusDamage(self, self->owner, 24, self, 48, MOD_ETF_RIFLE);
+	}
+
+	G_FreeEdict (self);
+}
+
+void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick)
+{
+	edict_t *flechette;
+
+	VectorNormalize (dir);
+
+	flechette = G_Spawn();
+	VectorCopy (start, flechette->s.origin);
+	VectorCopy (start, flechette->s.old_origin);
+	vectoangles2 (dir, flechette->s.angles);
+
+	VectorScale (dir, speed, flechette->velocity);
+	flechette->movetype = MOVETYPE_FLYMISSILE;
+	flechette->clipmask = MASK_SHOT;
+	flechette->solid = SOLID_BBOX;
+	flechette->s.renderfx = RF_FULLBRIGHT;
+	VectorClear (flechette->mins);
+	VectorClear (flechette->maxs);
+	
+	flechette->s.modelindex = gi.modelindex ("models/proj/flechette/tris.md2");
+
+//	flechette->s.sound = gi.soundindex ("");			// FIXME - correct sound!
+	flechette->owner = self;
+	flechette->touch = flechette_touch;
+	flechette->nextthink = level.time + 8000/speed;
+	flechette->think = G_FreeEdict;
+	flechette->dmg = damage;
+	flechette->dmg_radius = kick;
+
+	gi.linkentity (flechette);
+	
+	if (self->client)
+		check_dodge (self, flechette->s.origin, dir, speed);
+}
+#endif
+
+// **************************
+// PROX
+// **************************
+
+#ifdef INCLUDE_PROX
+#define PROX_TIME_TO_LIVE	45		// 45, 30, 15, 10
+#define PROX_TIME_DELAY		0.5
+#define PROX_BOUND_SIZE		96
+#define PROX_DAMAGE_RADIUS	192
+#define PROX_HEALTH			20
+#define	PROX_DAMAGE			90
+
+//===============
+//===============
+void Prox_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+	edict_t		*owner;
+
+// free the trigger field
+
+	//PMM - changed teammaster to "mover" .. owner of the field is the prox
+	if(ent->teamchain && ent->teamchain->owner == ent)
+		G_FreeEdict(ent->teamchain);
+
+	owner = ent;
+	if(ent->teammaster)
+	{
+		owner = ent->teammaster;
+		PlayerNoise(owner, ent->s.origin, PNOISE_IMPACT);
+	}
+
+	// play quad sound if appopriate
+	if (ent->dmg > PROX_DAMAGE)
+		gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+
+	ent->takedamage = DAMAGE_NO;
+	T_RadiusDamage(ent, owner, ent->dmg, ent, PROX_DAMAGE_RADIUS, MOD_PROX);
+
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	if (ent->groundentity)
+		gi.WriteByte (TE_GRENADE_EXPLOSION);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (ent);
+}
+
+//===============
+//===============
+void prox_die(edict_t *self, edict_t *inflictor, edict_t *, int, vec3_t)
+{
+//	gi.dprintf("prox_die\n");
+	// if set off by another prox, delay a little (chained explosions)
+	if (strcmp(inflictor->classname, "prox"))
+	{
+		self->takedamage = DAMAGE_NO;
+		Prox_Explode(self);
+	}
+	else
+	{
+		self->takedamage = DAMAGE_NO;
+		self->think = Prox_Explode;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+//===============
+//===============
+void Prox_Field_Touch (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t *prox;
+
+	if (!(other->svflags & SVF_MONSTER) && !other->client)
+		return;
+
+	// trigger the prox mine if it's still there, and still mine.
+	prox = ent->owner;
+
+	if (other == prox) // don't set self off
+		return;
+
+	if (prox->think == Prox_Explode) // we're set to blow!
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%f - prox already gone off!\n", level.time);
+		return;
+	}
+
+	if(prox->teamchain == ent)
+	{
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
+		prox->think = Prox_Explode;
+		prox->nextthink = level.time + PROX_TIME_DELAY;
+		return;
+	}
+
+	ent->solid = SOLID_NOT;
+	G_FreeEdict(ent);
+}
+
+//===============
+//===============
+void prox_seek (edict_t *ent)
+{
+	if(level.time > ent->wait)
+	{
+		Prox_Explode(ent);
+	}
+	else
+	{
+		ent->s.frame++;
+		if(ent->s.frame > 13)
+			ent->s.frame = 9;
+		ent->think = prox_seek;
+		ent->nextthink = level.time + 0.1;
+	}
+}
+
+//===============
+//===============
+void prox_open (edict_t *ent)
+{
+	edict_t *search;
+
+	search = NULL;
+//	gi.dprintf("prox_open %d\n", ent->s.frame);	
+//	gi.dprintf("%f\n", ent->velocity[2]);
+	if(ent->s.frame == 9)	// end of opening animation
+	{
+		// set the owner to NULL so the owner can shoot it, etc.  needs to be done here so the owner
+		// doesn't get stuck on it while it's opening if fired at point blank wall
+		ent->s.sound = 0;
+		ent->owner = NULL;
+		if(ent->teamchain)
+			ent->teamchain->touch = Prox_Field_Touch;
+		while ((search = findradius(search, ent->s.origin, PROX_DAMAGE_RADIUS+10)) != NULL)
+		{
+			if (!search->classname)			// tag token and other weird shit
+				continue;
+
+//			if (!search->takedamage)
+//				continue;
+			// if it's a monster or player with health > 0
+			// or it's a player start point
+			// and we can see it
+			// blow up
+			if (
+				(
+					(((search->svflags & SVF_MONSTER) || (search->client)) && (search->health > 0))	|| 
+					(
+						(deathmatch->value) && 
+						(
+						(!strcmp(search->classname, "info_player_deathmatch")) ||
+						(!strcmp(search->classname, "info_player_start")) ||
+						(!strcmp(search->classname, "info_player_coop")) ||
+						(!strcmp(search->classname, "misc_teleporter_dest"))
+						)
+					)
+				) 
+				&& (visible (search, ent))
+			   )
+			{
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxwarn.wav"), 1, ATTN_NORM, 0);
+				Prox_Explode (ent);
+				return;
+			}
+		}
+
+		if (strong_mines && (strong_mines->value))
+			ent->wait = level.time + PROX_TIME_TO_LIVE;
+		else
+		{
+			switch (ent->dmg/PROX_DAMAGE)
+			{
+				case 1:
+					ent->wait = level.time + PROX_TIME_TO_LIVE;
+					break;
+				case 2:
+					ent->wait = level.time + 30;
+					break;
+				case 4:
+					ent->wait = level.time + 15;
+					break;
+				case 8:
+					ent->wait = level.time + 10;
+					break;
+				default:
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("prox with unknown multiplier %d!\n", ent->dmg/PROX_DAMAGE);
+					ent->wait = level.time + PROX_TIME_TO_LIVE;
+					break;
+			}
+		}
+
+//		ent->wait = level.time + PROX_TIME_TO_LIVE;
+		ent->think = prox_seek;
+		ent->nextthink = level.time + 0.2;
+	}
+	else
+	{
+		if (ent->s.frame == 0)
+			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/proxopen.wav"), 1, ATTN_NORM, 0);
+		//ent->s.sound = gi.soundindex ("weapons/proxopen.wav");
+		ent->s.frame++;
+		ent->think = prox_open;
+		ent->nextthink = level.time + 0.05;	
+	}
+}
+
+//===============
+//===============
+void prox_land (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	edict_t	*field;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+	int	stick_ok;
+	int		movetype = MOVETYPE_NONE;
+	vec3_t	land_point;
+
+	// must turn off owner so owner can shoot it and set it off
+	// moved to prox_open so owner can get away from it if fired at pointblank range into
+	// wall
+//	ent->owner = NULL;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("land - %2.2f %2.2f %2.2f\n", ent->velocity[0], ent->velocity[1], ent->velocity[2]);
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict(ent);
+		return;
+	}
+
+	if (plane->normal)
+	{
+		VectorMA (ent->s.origin, -10.0, plane->normal, land_point);
+		if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			Prox_Explode (ent);
+			return;
+		}
+	}
+
+	if ((other->svflags & SVF_MONSTER) || other->client || (other->svflags & SVF_DAMAGEABLE))
+	{
+		if(other != ent->teammaster)
+			Prox_Explode(ent);
+
+		return;
+	}
+
+#define STOP_EPSILON	0.1
+
+	else if (other != WORLD)
+	{
+		//Here we need to check to see if we can stop on this entity.
+		//Note that plane can be NULL
+
+		//PMM - code stolen from g_phys (ClipVelocity)
+		vec3_t out;
+		float backoff, change;
+		int i;
+
+		if (!plane->normal) // this happens if you hit a point object, maybe other cases
+		{
+			// Since we can't tell what's going to happen, just blow up
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("bad normal for surface, exploding!\n");
+
+			Prox_Explode(ent);
+			return;
+		}
+
+		if ((other->movetype == MOVETYPE_PUSH) && (plane->normal[2] > 0.7))
+			stick_ok = 1;
+		else
+			stick_ok = 0;
+
+		backoff = DotProduct (ent->velocity, plane->normal) * 1.5;
+		for (i=0 ; i<3 ; i++)
+		{
+			change = plane->normal[i]*backoff;
+			out[i] = ent->velocity[i] - change;
+			if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
+				out[i] = 0;
+		}
+
+		if (out[2] > 60)
+			return;
+
+		//movetype = MOVETYPE_BOUNCE;
+
+		// if we're here, we're going to stop on an entity
+		if (stick_ok)
+		{ // it's a happy entity
+			VectorCopy (vec3_origin, ent->velocity);
+			VectorCopy (vec3_origin, ent->avelocity);
+		}
+		else // no-stick.  teflon time
+		{
+			if (plane->normal[2] > 0.7)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("stuck on entity, blowing up!\n");
+
+				Prox_Explode(ent);
+				return;
+			}
+			return;
+		}
+	}
+	else if (other->s.modelindex != 1)
+		return;
+
+	vectoangles2 (plane->normal, dir);
+	AngleVectors (dir, forward, right, up);
+
+	if (gi.pointcontents (ent->s.origin) & (CONTENTS_LAVA|CONTENTS_SLIME))
+	{
+		Prox_Explode (ent);
+		return;
+	}
+
+	field = G_Spawn();
+
+	VectorCopy (ent->s.origin, field->s.origin);
+	VectorClear(field->velocity);
+	VectorClear(field->avelocity);
+	VectorSet(field->mins, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE, -PROX_BOUND_SIZE);
+	VectorSet(field->maxs, PROX_BOUND_SIZE, PROX_BOUND_SIZE, PROX_BOUND_SIZE);
+	field->movetype = MOVETYPE_NONE;
+	field->solid = SOLID_TRIGGER;
+	field->owner = ent;
+	field->classname = "prox_field";
+	field->teammaster = ent;
+	gi.linkentity (field);
+
+	VectorClear(ent->velocity);
+	VectorClear(ent->avelocity);
+	// rotate to vertical
+	dir[PITCH] = dir[PITCH] + 90;
+	VectorCopy (dir, ent->s.angles);
+	ent->takedamage = DAMAGE_AIM;
+	ent->movetype = movetype;		// either bounce or none, depending on whether we stuck to something
+	ent->die = prox_die;
+	ent->teamchain = field;
+	ent->health = PROX_HEALTH;
+	ent->nextthink = level.time + 0.05;
+	ent->think = prox_open;
+	ent->touch = NULL;
+	ent->solid = SOLID_BBOX;
+	// record who we're attached to
+//	ent->teammaster = other;
+
+	gi.linkentity(ent);
+}
+
+//===============
+//===============
+void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
+{
+	edict_t	*prox;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("start %s    aim %s   speed %d\n", vtos(start), vtos(aimdir), speed);
+	prox = G_Spawn();
+	VectorCopy (start, prox->s.origin);
+	VectorScale (aimdir, speed, prox->velocity);
+	VectorMA (prox->velocity, 200 + crandom() * 10.0, up, prox->velocity);
+	VectorMA (prox->velocity, crandom() * 10.0, right, prox->velocity);
+	VectorCopy (dir, prox->s.angles);
+	prox->s.angles[PITCH]-=90;
+	prox->movetype = MOVETYPE_BOUNCE;
+	prox->solid = SOLID_BBOX; 
+	prox->s.effects |= EF_GRENADE;
+	prox->clipmask = MASK_SHOT|CONTENTS_LAVA|CONTENTS_SLIME;
+	prox->s.renderfx |= RF_IR_VISIBLE;
+	//FIXME - this needs to be bigger.  Has other effects, though.  Maybe have to change origin to compensate
+	// so it sinks in correctly.  Also in lavacheck, might have to up the distance
+	VectorSet (prox->mins, -6, -6, -6);
+	VectorSet (prox->maxs, 6, 6, 6);
+	prox->s.modelindex = gi.modelindex ("models/weapons/g_prox/tris.md2");
+	prox->owner = self;
+	prox->teammaster = self;
+	prox->touch = prox_land;
+//	prox->nextthink = level.time + PROX_TIME_TO_LIVE;
+	prox->think = Prox_Explode;
+	prox->dmg = PROX_DAMAGE*damage_multiplier;
+	prox->classname = "prox";
+	prox->svflags |= SVF_DAMAGEABLE;
+	prox->flags |= FL_MECHANICAL;
+
+	switch (damage_multiplier)
+	{
+	case 1:
+		prox->nextthink = level.time + PROX_TIME_TO_LIVE;
+		break;
+	case 2:
+		prox->nextthink = level.time + 30;
+		break;
+	case 4:
+		prox->nextthink = level.time + 15;
+		break;
+	case 8:
+		prox->nextthink = level.time + 10;
+		break;
+	default:
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("prox with unknown multiplier %d!\n", damage_multiplier);
+		prox->nextthink = level.time + PROX_TIME_TO_LIVE;
+		break;
+	}
+
+	gi.linkentity (prox);
+}
+#endif
+
+// *************************
+// FLAMETHROWER
+// *************************
+
+#ifdef INCLUDE_FLAMETHROWER
+#define FLAMETHROWER_RADIUS		8
+
+void fire_remove (edict_t *ent)
+{
+	if(ent == ent->owner->teamchain)
+		ent->owner->teamchain = NULL;
+
+	G_FreeEdict(ent);	
+}
+
+void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
+{
+	edict_t *flame;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	flame = G_Spawn();
+
+	// the origin is the first control point, put it speed forward.
+	VectorMA(start, speed, forward, flame->s.origin);
+
+	// record that velocity
+	VectorScale (aimdir, speed, flame->velocity);
+
+	VectorCopy (dir, flame->s.angles);
+	flame->movetype = MOVETYPE_NONE;
+	flame->solid = SOLID_NOT;
+
+	VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
+	VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
+
+	flame->s.sound = gi.soundindex ("weapons/flame.wav");
+	flame->owner = self;
+	flame->dmg = damage;
+	flame->classname = "flame";
+
+	// clear control points and velocities
+	VectorCopy (flame->s.origin, flame->flameinfo.pos1);
+	VectorCopy (flame->velocity, flame->flameinfo.vel1);
+	VectorCopy (flame->s.origin, flame->flameinfo.pos2);
+	VectorCopy (flame->velocity, flame->flameinfo.vel2);
+	VectorCopy (flame->s.origin, flame->flameinfo.pos3);
+	VectorCopy (flame->velocity, flame->flameinfo.vel3);
+	VectorCopy (flame->s.origin, flame->flameinfo.pos4);
+
+	// hook flame stream to owner
+	self->teamchain = flame;
+
+	gi.linkentity (flame);
+}
+
+// fixme - change to use start location, not entity origin
+void fire_maintain (edict_t *ent, edict_t *flame, vec3_t start, vec3_t aimdir, int damage, int speed)
+{
+	trace_t	tr;
+
+	// move the control points out the appropriate direction and velocity
+	VectorAdd(flame->flameinfo.pos3, flame->flameinfo.vel3, flame->flameinfo.pos4);
+	VectorAdd(flame->flameinfo.pos2, flame->flameinfo.vel2, flame->flameinfo.pos3);
+	VectorAdd(flame->flameinfo.pos1, flame->flameinfo.vel1, flame->flameinfo.pos2);
+	VectorAdd(flame->s.origin,		 flame->velocity,       flame->flameinfo.pos1);
+
+	// move the velocities for the control points
+	VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
+	VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
+	VectorCopy(flame->velocity,		  flame->flameinfo.vel1);
+
+	// set velocity and location for new control point 0.
+	VectorMA(start, speed, aimdir, flame->s.origin);
+	VectorScale(aimdir, speed, flame->velocity);
+	
+	//
+	// does it hit a wall? if so, when?
+	//
+
+	// player fire point to flame origin.
+	tr = gi.trace(start, flame->mins, flame->maxs,
+					flame->s.origin, flame, MASK_SHOT);
+	if(tr.fraction == 1.0)
+	{
+		// origin to point 1
+		tr = gi.trace(flame->s.origin, flame->mins, flame->maxs,
+						flame->flameinfo.pos1, flame, MASK_SHOT);
+		if(tr.fraction == 1.0)
+		{
+			// point 1 to point 2
+			tr = gi.trace(flame->flameinfo.pos1, flame->mins, flame->maxs,
+							flame->flameinfo.pos2, flame, MASK_SHOT);
+			if(tr.fraction == 1.0)
+			{
+				// point 2 to point 3
+				tr = gi.trace(flame->flameinfo.pos2, flame->mins, flame->maxs,
+							flame->flameinfo.pos3, flame, MASK_SHOT);
+				if(tr.fraction == 1.0)
+				{
+					// point 3 to point 4, point 3 valid
+					tr = gi.trace(flame->flameinfo.pos3, flame->mins, flame->maxs,
+								flame->flameinfo.pos4, flame, MASK_SHOT);
+					if(tr.fraction < 1.0) // point 4 blocked
+					{
+						VectorCopy(tr.endpos, flame->flameinfo.pos4);
+					}
+				}
+				else	// point 3 blocked, point 2 valid
+				{
+					VectorCopy(flame->flameinfo.vel2, flame->flameinfo.vel3);
+					VectorCopy(tr.endpos, flame->flameinfo.pos3);
+					VectorCopy(tr.endpos, flame->flameinfo.pos4);
+				}
+			}
+			else	// point 2 blocked, point 1 valid
+			{
+				VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel2);
+				VectorCopy(flame->flameinfo.vel1, flame->flameinfo.vel3);
+				VectorCopy(tr.endpos, flame->flameinfo.pos2);
+				VectorCopy(tr.endpos, flame->flameinfo.pos3);
+				VectorCopy(tr.endpos, flame->flameinfo.pos4);
+			}
+		}
+		else	// point 1 blocked, origin valid
+		{
+			VectorCopy(flame->velocity, flame->flameinfo.vel1);
+			VectorCopy(flame->velocity, flame->flameinfo.vel2);
+			VectorCopy(flame->velocity, flame->flameinfo.vel3);
+			VectorCopy(tr.endpos, flame->flameinfo.pos1);
+			VectorCopy(tr.endpos, flame->flameinfo.pos2);
+			VectorCopy(tr.endpos, flame->flameinfo.pos3);
+			VectorCopy(tr.endpos, flame->flameinfo.pos4);
+		}
+	}
+	else // origin blocked!
+	{
+//		gi.dprintf("point 2 blocked\n");
+		VectorCopy(flame->velocity, flame->flameinfo.vel1);
+		VectorCopy(flame->velocity, flame->flameinfo.vel2);
+		VectorCopy(flame->velocity, flame->flameinfo.vel3);
+		VectorCopy(tr.endpos, flame->s.origin);
+		VectorCopy(tr.endpos, flame->flameinfo.pos1);
+		VectorCopy(tr.endpos, flame->flameinfo.pos2);
+		VectorCopy(tr.endpos, flame->flameinfo.pos3);
+		VectorCopy(tr.endpos, flame->flameinfo.pos4);
+	}
+	
+	if(tr.fraction < 1.0 && tr.ent->takedamage)
+	{
+		T_Damage (tr.ent, flame, ent, flame->velocity, tr.endpos, tr.plane.normal, 
+					damage, 0, DAMAGE_NO_KNOCKBACK | DAMAGE_ENERGY | DAMAGE_FIRE);
+	}
+
+	gi.linkentity(flame);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_FLAME);
+	gi.WriteShort(ent - g_edicts);
+	gi.WriteShort(6);
+	gi.WritePosition (start);
+	gi.WritePosition (flame->s.origin);
+	gi.WritePosition (flame->flameinfo.pos1);
+	gi.WritePosition (flame->flameinfo.pos2);
+	gi.WritePosition (flame->flameinfo.pos3);
+	gi.WritePosition (flame->flameinfo.pos4);
+	gi.multicast (flame->s.origin, MULTICAST_PVS);
+}
+
+/*QUAKED trap_flameshooter (1 0 0) (-8 -8 -8) (8 8 8)
+*/
+#define FLAMESHOOTER_VELOCITY			50
+#define FLAMESHOOTER_DAMAGE				20
+#define FLAMESHOOTER_BURST_VELOCITY		300
+#define FLAMESHOOTER_BURST_DAMAGE		30
+
+//#define FLAMESHOOTER_PUFF	1
+#define FLAMESHOOTER_STREAM	1
+
+void flameshooter_think (edict_t *self)
+{
+	vec3_t	forward, right, up;
+	edict_t *flame;
+	
+	if(self->delay)
+	{
+		if(self->teamchain)
+			fire_remove (self->teamchain);
+		return;
+	}
+
+	self->s.angles[1] += self->speed;
+	if(self->s.angles[1] > 135 || self->s.angles[1] < 45)
+		self->speed = -self->speed;
+		 
+	AngleVectors (self->s.angles, forward, right, up);
+
+#ifdef FLAMESHOOTER_STREAM
+	flame = self->teamchain;
+	if(!self->teamchain)
+		fire_flame (self, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
+	else
+		fire_maintain (self, flame, self->s.origin, forward, FLAMESHOOTER_DAMAGE, FLAMESHOOTER_VELOCITY);
+
+	self->think = flameshooter_think;
+	self->nextthink = level.time + 0.05;
+#else
+	fire_burst (self, self->s.origin, forward, FLAMESHOOTER_BURST_DAMAGE, FLAMESHOOTER_BURST_VELOCITY);
+	
+	self->think = flameshooter_think;
+	self->nextthink = level.time + 0.1;
+#endif
+}
+
+void flameshooter_use (edict_t *self, edict_t *other, edict_t *activator)
+{
+	if(self->delay)
+	{
+		self->delay = 0;
+		self->think = flameshooter_think;
+		self->nextthink = level.time + 0.1;
+	}	
+	else
+		self->delay = 1;
+}
+
+void SP_trap_flameshooter(edict_t *self)
+{
+	vec3_t	tempAngles;
+
+	self->solid = SOLID_NOT;
+	self->movetype = MOVETYPE_NONE;
+
+	self->delay = 0;
+
+	self->use =	flameshooter_use;
+	if(self->delay == 0)
+	{
+		self->think = flameshooter_think;
+		self->nextthink = level.time  + 0.1;
+	}
+
+//	self->flags |= FL_NOCLIENT;
+
+	self->speed = 10;
+
+//	self->speed = 0;	// FIXME this stops the spraying
+
+	VectorCopy(self->s.angles, tempAngles);
+
+	if (!VectorCompare(self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	VectorCopy(tempAngles, self->s.angles);
+
+//	gi.setmodel (self, self->model);
+	gi.linkentity (self);
+}
+
+// *************************
+// fire_burst
+// *************************
+
+#define FLAME_BURST_MAX_SIZE	64
+#define FLAME_BURST_FRAMES		20
+#define FLAME_BURST_MIDPOINT	10
+
+void fire_burst_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	int		powerunits;
+	int		damage, radius;
+	vec3_t	origin;
+	
+	if (surf && (surf->flags & SURF_SKY))
+	{
+//		gi.dprintf("Hit sky. Removed\n");
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if(other == ent->owner || ent == other)
+		return;
+
+	// don't let flame puffs blow each other up
+	if(other->classname && !strcmp(other->classname, ent->classname))
+		return;
+
+	if(ent->waterlevel)
+	{
+//		gi.dprintf("Hit water. Removed\n");
+		G_FreeEdict(ent);		
+	}
+
+	if(!(other->svflags & SVF_MONSTER) && !other->client)
+	{
+		powerunits = FLAME_BURST_FRAMES - ent->s.frame;
+		damage = powerunits * 6;
+		radius = powerunits * 4;
+
+//		T_RadiusDamage (inflictor, attacker, damage, ignore, radius)
+		T_RadiusDamage(ent, ent->owner, damage, ent, radius, DAMAGE_FIRE);
+
+//		gi.dprintf("Hit world: %d pts, %d rad\n", damage, radius);
+
+		// calculate position for the explosion entity
+		VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_PLAIN_EXPLOSION);
+		gi.WritePosition (origin);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		G_FreeEdict (ent);
+	}
+}
+
+void fire_burst_think (edict_t *self)
+{
+	int	current_radius;
+
+	if(self->waterlevel)
+	{
+		G_FreeEdict(self);
+		return;
+	}
+
+	self->s.frame++;
+	if(self->s.frame >= FLAME_BURST_FRAMES)
+	{
+		G_FreeEdict(self);
+		return;
+	}
+
+	else if(self->s.frame < FLAME_BURST_MIDPOINT)
+	{
+		current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * self->s.frame;
+	}
+	else
+	{
+		current_radius = (FLAME_BURST_MAX_SIZE / FLAME_BURST_MIDPOINT) * (FLAME_BURST_FRAMES - self->s.frame);
+	}
+
+	if(self->s.frame == 3)
+		self->s.skinnum = 1;
+	else if (self->s.frame == 7)
+		self->s.skinnum = 2;
+	else if (self->s.frame == 10)
+		self->s.skinnum = 3;
+	else if (self->s.frame == 13)
+		self->s.skinnum = 4;
+	else if (self->s.frame == 16)
+		self->s.skinnum = 5;
+	else if (self->s.frame == 19)
+		self->s.skinnum = 6;
+
+	if(current_radius < 8)
+		current_radius = 8;
+	else if(current_radius > FLAME_BURST_MAX_SIZE)
+		current_radius = FLAME_BURST_MAX_SIZE;
+
+	T_RadiusDamage(self, self->owner, self->dmg, self, current_radius, DAMAGE_FIRE);
+
+	self->think = fire_burst_think;
+	self->nextthink = level.time + 0.1;
+}
+
+void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed)
+{
+	edict_t *flame;
+	vec3_t	dir;
+	vec3_t	baseVel;
+	vec3_t	forward, right, up;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	flame = G_Spawn();
+	VectorCopy(start, flame->s.origin);
+//	VectorScale (aimdir, speed, flame->velocity);
+
+	// scale down so only 30% of player's velocity is taken into account.
+	VectorScale (self->velocity, 0.3, baseVel);
+	VectorMA(baseVel, speed, aimdir, flame->velocity);
+
+	VectorCopy (dir, flame->s.angles);
+	flame->movetype = MOVETYPE_FLY;
+	flame->solid = SOLID_TRIGGER;
+
+	VectorSet(flame->mins, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS, -FLAMETHROWER_RADIUS);
+	VectorSet(flame->maxs, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS, FLAMETHROWER_RADIUS);
+
+	flame->s.sound = gi.soundindex ("weapons/flame.wav");
+	flame->s.modelindex = gi.modelindex ("models/projectiles/puff/tris.md2");
+	flame->owner = self;
+	flame->touch = fire_burst_touch;
+	flame->think = fire_burst_think;
+	flame->nextthink = level.time + 0.1;
+	flame->dmg = damage;
+	flame->classname = "flameburst";
+	flame->s.effects = EF_FIRE_PUFF;
+
+	gi.linkentity (flame);
+}
+#endif
+
+// *************************
+//	INCENDIARY GRENADES
+// *************************
+
+#ifdef INCLUDE_INCENDIARY
+void FireThink (edict_t *ent)
+{
+	if(level.time > ent->wait)
+		G_FreeEdict(ent);
+	else
+	{
+		ent->s.frame++;
+		if(ent->s.frame>10)
+			ent->s.frame = 0;
+		ent->nextthink = level.time + 0.05;
+		ent->think = FireThink;
+	}
+}
+
+#define FIRE_HEIGHT		64
+#define FIRE_RADIUS		64
+#define FIRE_DAMAGE		3
+#define FIRE_DURATION	15
+
+edict_t *StartFire(edict_t *fireOwner, vec3_t fireOrigin, float fireDuration, float fireDamage)
+{
+	edict_t	*fire;
+
+	fire = G_Spawn();
+	VectorCopy (fireOrigin, fire->s.origin);
+	fire->movetype = MOVETYPE_TOSS;
+	fire->solid = SOLID_TRIGGER;
+	VectorSet(fire->mins, -FIRE_RADIUS, -FIRE_RADIUS, 0);
+	VectorSet(fire->maxs, FIRE_RADIUS, FIRE_RADIUS, FIRE_HEIGHT);
+
+	fire->s.sound = gi.soundindex ("weapons/incend.wav");
+	fire->s.modelindex = gi.modelindex ("models/objects/fire/tris.md2");
+
+	fire->owner = fireOwner;
+	fire->touch = hurt_touch;
+	fire->nextthink = level.time + 0.05;
+	fire->wait = level.time + fireDuration;
+	fire->think = FireThink;
+//	fire->nextthink = level.time + fireDuration;
+//	fire->think = G_FreeEdict;
+	fire->dmg = fireDamage;
+	fire->classname = "incendiary_fire";
+	
+	gi.linkentity (fire);
+
+//	gi.sound (fire, CHAN_VOICE, gi.soundindex ("weapons/incend.wav"), 1, ATTN_NORM, 0);
+	return fire;
+}
+
+static void Incendiary_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	//FIXME: if we are onground then raise our Z just a bit since we are a point?
+	T_RadiusDamage(ent, ent->owner, ent->dmg, NULL, ent->dmg_radius, DAMAGE_FIRE);
+
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	if (ent->groundentity)
+		gi.WriteByte (TE_GRENADE_EXPLOSION);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	StartFire(ent->owner, ent->s.origin, FIRE_DURATION, FIRE_DAMAGE);
+
+	G_FreeEdict (ent);
+
+}
+
+static void Incendiary_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!(other->svflags & SVF_MONSTER) && !(ent->client))
+//	if (!other->takedamage)
+	{
+		if (ent->spawnflags & 1)
+		{
+			if (random() > 0.5)
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+			else
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+		}
+		else
+		{
+			if (random() > 0.5)
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
+			else
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb2b.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+
+	Incendiary_Explode (ent);
+}
+
+void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+//	if (self->client)
+//		grenade->s.effects &= ~EF_TELEPORT;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/projectiles/incend/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Incendiary_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Incendiary_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "incendiary_grenade";
+
+	gi.linkentity (grenade);
+}
+#endif
+
+// *************************
+// MELEE WEAPONS
+// *************************
+
+#ifdef INCLUDE_MELEE
+void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod)
+{
+	vec3_t		forward, right, up;
+	vec3_t		v;
+	vec3_t		point;
+	trace_t		tr;
+
+	vectoangles2 (aim, v);
+	AngleVectors (v, forward, right, up);
+	VectorNormalize (forward);
+	VectorMA( start, reach, forward, point);
+
+	//see if the hit connects
+	tr = gi.trace(start, NULL, NULL, point, self, MASK_SHOT);
+	if(tr.fraction ==  1.0)
+	{
+		if(!quiet)
+			gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/swish.wav"), 1, ATTN_NORM, 0);
+		//FIXME some sound here?
+		return;
+	}
+
+	if(tr.ent->takedamage == DAMAGE_YES || tr.ent->takedamage == DAMAGE_AIM)
+	{
+		// pull the player forward if you do damage
+		VectorMA(self->velocity, 75, forward, self->velocity);
+		VectorMA(self->velocity, 75, up, self->velocity);
+
+		// do the damage
+		// FIXME - make the damage appear at right spot and direction
+		if(mod == MOD_CHAINFIST)
+			T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, 
+						DAMAGE_DESTROY_ARMOR | DAMAGE_NO_KNOCKBACK, mod);
+		else
+			T_Damage (tr.ent, self, self, vec3_origin, tr.ent->s.origin, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, mod);
+
+		if(!quiet)
+			gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/meatht.wav"), 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		if(!quiet)
+			gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/tink1.wav"), 1, ATTN_NORM, 0);
+
+		VectorScale (tr.plane.normal, 256, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_GUNSHOT);
+		gi.WritePosition (tr.endpos);
+		gi.WriteDir (point);
+		gi.multicast (tr.endpos, MULTICAST_PVS);
+	}
+}
+#endif
+
+// *************************
+// NUKE 
+// *************************
+
+#ifdef INCLUDE_NUKE
+#define	NUKE_DELAY			4
+#define NUKE_TIME_TO_LIVE	6
+//#define NUKE_TIME_TO_LIVE	40
+#define NUKE_RADIUS			512
+#define NUKE_DAMAGE			400
+#define	NUKE_QUAKE_TIME		3
+#define NUKE_QUAKE_STRENGTH	100
+
+void Nuke_Quake (edict_t *self)
+{
+	int		i;
+	edict_t	*e;
+
+	if (self->last_move_time < level.time)
+	{
+		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 0.75, ATTN_NONE, 0);
+		self->last_move_time = level.time + 0.5;
+	}
+
+	for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->client)
+			continue;
+		if (!e->groundentity)
+			continue;
+
+		e->groundentity = NULL;
+		e->velocity[0] += crandom()* 150;
+		e->velocity[1] += crandom()* 150;
+		e->velocity[2] = self->speed * (100.0 / e->mass);
+	}
+
+	if (level.time < self->timestamp)
+		self->nextthink = level.time + FRAMETIME;
+	else
+		G_FreeEdict (self);
+}
+
+
+static void Nuke_Explode (edict_t *ent)
+{
+//	vec3_t		origin;
+
+//	nuke_framenum = level.framenum + 20;
+
+	if (ent->teammaster->client)
+		PlayerNoise(ent->teammaster, ent->s.origin, PNOISE_IMPACT);
+
+	T_RadiusNukeDamage(ent, ent->teammaster, ent->dmg, ent, ent->dmg_radius, MOD_NUKE);
+
+//	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	if (ent->dmg > NUKE_DAMAGE)
+		gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+
+	gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/grenlx1a.wav"), 1, ATTN_NONE, 0);
+/*
+	gi.WriteByte (svc_temp_entity);
+	if (ent->groundentity)
+		gi.WriteByte (TE_GRENADE_EXPLOSION);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+*/
+
+	//	BecomeExplosion1(ent);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1_BIG);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_NUKEBLAST);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_ALL);
+
+	// become a quake
+	ent->svflags |= SVF_NOCLIENT;
+	ent->noise_index = gi.soundindex ("world/rumble.wav");
+	ent->think = Nuke_Quake;
+	ent->speed = NUKE_QUAKE_STRENGTH;
+	ent->timestamp = level.time + NUKE_QUAKE_TIME;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->last_move_time = 0;
+}
+
+void nuke_die(edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	if ((attacker) && !(strcmp(attacker->classname, "nuke")))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("nuke nuked by a nuke, not nuking\n");
+		G_FreeEdict (self);	
+		return;
+	}
+	Nuke_Explode(self);
+}
+
+void Nuke_Think(edict_t *ent)
+{
+	float attenuation, default_atten = 1.8;
+	int		damage_multiplier, muzzleflash;
+
+//	gi.dprintf ("player range: %2.2f    damage radius: %2.2f\n", realrange (ent, ent->teammaster), ent->dmg_radius*2);
+
+	damage_multiplier = ent->dmg/NUKE_DAMAGE;
+	switch (damage_multiplier)
+	{
+	case 1:
+		attenuation = default_atten/1.4;
+		muzzleflash = MZ_NUKE1;
+		break;
+	case 2:
+		attenuation = default_atten/2.0;
+		muzzleflash = MZ_NUKE2;
+		break;
+	case 4:
+		attenuation = default_atten/3.0;
+		muzzleflash = MZ_NUKE4;
+		break;
+	case 8:
+		attenuation = default_atten/5.0;
+		muzzleflash = MZ_NUKE8;
+		break;
+	default:
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("default attenuation used for nuke!\n");
+		attenuation = default_atten;
+		muzzleflash = MZ_NUKE1;
+		break;
+	}
+
+	if(ent->wait < level.time)
+		Nuke_Explode(ent);
+	else if (level.time >= (ent->wait - NUKE_TIME_TO_LIVE))
+	{
+		ent->s.frame++;
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("nuke frame %d\n", ent->s.frame);
+		if(ent->s.frame > 11)
+			ent->s.frame = 6;
+
+		if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			Nuke_Explode (ent);
+			return;
+		}
+
+		ent->think = Nuke_Think;
+		ent->nextthink = level.time + 0.1;
+		ent->health = 1;
+		ent->owner = NULL;
+
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (muzzleflash);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		if (ent->timestamp <= level.time)
+		{
+/*			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn.wav"), 1, ATTN_NORM, 0);
+			ent->timestamp += 10.0;
+		}
+*/		
+
+			if ((ent->wait - level.time) <= (NUKE_TIME_TO_LIVE/2.0))
+			{
+//				ent->s.sound = gi.soundindex ("weapons/nukewarn.wav");
+//				gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
+				gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
+//				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
+//				gi.dprintf ("time %2.2f\n", ent->wait-level.time);
+				ent->timestamp = level.time + 0.3;
+			}
+			else
+			{
+				gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
+//				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
+				ent->timestamp = level.time + 0.5;
+//				gi.dprintf ("time %2.2f\n", ent->wait-level.time);
+			}
+		}
+	}
+	else
+	{
+		if (ent->timestamp <= level.time)
+		{
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, attenuation, 0);
+//			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/nukewarn2.wav"), 1, ATTN_NORM, 0);
+//				gi.dprintf ("time %2.2f\n", ent->wait-level.time);
+			ent->timestamp = level.time + 1.0;
+		}
+		ent->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void nuke_bounce (edict_t *ent, edict_t *, cplane_t *, csurface_t *)
+{
+	if (qrandom() > 0.5)
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+	else
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+}
+
+
+extern byte P_DamageModifier(edict_t *ent);
+
+void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed)
+{
+	edict_t	*nuke;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+	int		damage_modifier;
+
+	damage_modifier = (int) P_DamageModifier (self);
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	nuke = G_Spawn();
+	VectorCopy (start, nuke->s.origin);
+	VectorScale (aimdir, speed, nuke->velocity);
+
+	VectorMA (nuke->velocity, 200 + crandom() * 10.0, up, nuke->velocity);
+	VectorMA (nuke->velocity, crandom() * 10.0, right, nuke->velocity);
+	VectorClear (nuke->avelocity);
+	VectorClear (nuke->s.angles);
+	nuke->movetype = MOVETYPE_BOUNCE;
+	nuke->clipmask = MASK_SHOT;
+	nuke->solid = SOLID_BBOX;
+	nuke->s.effects |= EF_GRENADE;
+	nuke->s.renderfx |= RF_IR_VISIBLE;
+	VectorSet (nuke->mins, -8, -8, 0);
+	VectorSet (nuke->maxs, 8, 8, 16);
+	nuke->s.modelindex = gi.modelindex ("models/weapons/g_nuke/tris.md2");
+	nuke->owner = self;
+	nuke->teammaster = self;
+	nuke->nextthink = level.time + FRAMETIME;
+	nuke->wait = level.time + NUKE_DELAY + NUKE_TIME_TO_LIVE;
+	nuke->think = Nuke_Think;
+	nuke->touch = nuke_bounce;
+
+	nuke->health = 10000;
+	nuke->takedamage = DAMAGE_YES;
+	nuke->svflags |= SVF_DAMAGEABLE;
+	nuke->dmg = NUKE_DAMAGE * damage_modifier;
+	if (damage_modifier == 1)
+		nuke->dmg_radius = NUKE_RADIUS;
+	else
+		nuke->dmg_radius = NUKE_RADIUS + NUKE_RADIUS*(0.25*(float)damage_modifier);
+	// this yields 1.0, 1.5, 2.0, 3.0 times radius
+	
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("nuke modifier = %d, damage = %d, radius = %f\n", damage_modifier, nuke->dmg, nuke->dmg_radius);
+
+	nuke->classname = "nuke";
+	nuke->die = nuke_die;
+
+	gi.linkentity (nuke);
+}
+#endif
+
+// *************************
+// TESLA
+// *************************
+
+#ifdef INCLUDE_TESLA
+#define TESLA_TIME_TO_LIVE		30
+#define TESLA_DAMAGE_RADIUS		128
+#define TESLA_DAMAGE			3		// 3
+#define TESLA_KNOCKBACK			8
+
+#define	TESLA_ACTIVATE_TIME		3
+
+#define TESLA_EXPLOSION_DAMAGE_MULT		50		// this is the amount the damage is multiplied by for underwater explosions
+#define	TESLA_EXPLOSION_RADIUS			200
+
+void tesla_remove (edict_t *self)
+{
+	edict_t		*cur, *next;
+
+	self->takedamage = DAMAGE_NO;
+	if(self->teamchain)
+	{
+		cur = self->teamchain;
+		while(cur)
+		{
+			next = cur->teamchain;
+			G_FreeEdict ( cur );
+			cur = next;
+		}
+	}
+	else if (self->air_finished)
+		gi.dprintf ("tesla without a field!\n");
+
+	self->owner = self->teammaster;	// Going away, set the owner correctly.
+	// PGM - grenade explode does damage to self->enemy
+	self->enemy = NULL;
+
+	// play quad sound if quadded and an underwater explosion
+	if ((self->dmg_radius) && (self->dmg > (TESLA_DAMAGE*TESLA_EXPLOSION_DAMAGE_MULT)))
+		gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+
+	Grenade_Explode(self);
+}
+
+void tesla_die(edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+//	gi.dprintf("tesla killed\n");
+	tesla_remove(self);
+}
+
+void tesla_blow (edict_t *self)
+{
+//	T_RadiusDamage(self, self->owner, TESLA_EXPLOSION_DAMAGE, NULL, TESLA_EXPLOSION_RADIUS, MOD_TESLA);
+	self->dmg = self->dmg * TESLA_EXPLOSION_DAMAGE_MULT;
+	self->dmg_radius = TESLA_EXPLOSION_RADIUS;
+	tesla_remove(self);
+}
+
+
+void tesla_zap (edict_t *, edict_t *, cplane_t *, csurface_t *)
+{
+}
+
+void tesla_think_active (edict_t *self)
+{
+	int		i,num;
+	edict_t	*touch[MAX_EDICTS], *hit;
+	vec3_t	dir, start;
+	trace_t	tr;
+	
+	if(level.time > self->air_finished)
+	{
+		tesla_remove(self);
+		return;
+	}
+
+	VectorCopy(self->s.origin, start);
+	start[2] += 16;
+
+	num = gi.BoxEdicts(self->teamchain->absmin, self->teamchain->absmax, touch, MAX_EDICTS, AREA_SOLID);
+	for(i=0;i<num;i++)
+	{
+		// if the tesla died while zapping things, stop zapping.
+		if(!(self->inuse))
+			break;
+
+		hit=touch[i];
+		if(!hit->inuse)
+			continue;
+		if(hit == self)
+			continue;
+		if(hit->health < 1)
+			continue;
+		// don't hit clients in single-player or coop
+		if(hit->client)
+			if (coop->value || !deathmatch->value)
+				continue;
+		if(!(hit->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)) && !hit->client)
+			continue;
+	
+		tr = gi.trace(start, vec3_origin, vec3_origin, hit->s.origin, self, MASK_SHOT);
+		if(tr.fraction==1 || tr.ent==hit)// || tr.ent->client || (tr.ent->svflags & (SVF_MONSTER | SVF_DAMAGEABLE)))
+		{
+			VectorSubtract(hit->s.origin, start, dir);
+			
+			// PMM - play quad sound if it's above the "normal" damage
+			if (self->dmg > TESLA_DAMAGE)
+				gi.sound(self, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+
+			// PGM - don't do knockback to walking monsters
+			if((hit->svflags & SVF_MONSTER) && !(hit->flags & (FL_FLY|FL_SWIM)))
+				T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
+					self->dmg, 0, 0, MOD_TESLA);
+			else
+				T_Damage (hit, self, self->teammaster, dir, tr.endpos, tr.plane.normal,
+					self->dmg, TESLA_KNOCKBACK, 0, MOD_TESLA);
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_LIGHTNING);
+			gi.WriteShort (hit - g_edicts);			// destination entity
+			gi.WriteShort (self - g_edicts);		// source entity
+			gi.WritePosition (tr.endpos);
+			gi.WritePosition (start);
+			gi.multicast (start, MULTICAST_PVS);
+		}
+	}
+
+	if(self->inuse)
+	{
+		self->think = tesla_think_active;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void tesla_activate (edict_t *self)
+{
+	edict_t		*trigger;
+	edict_t		*search;
+
+	if (gi.pointcontents (self->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WATER))
+	{
+		tesla_blow (self);
+		return;
+	}
+
+	// only check for spawn points in deathmatch
+	if (deathmatch->value)
+	{
+		search = NULL;
+		while ((search = findradius(search, self->s.origin, 1.5*TESLA_DAMAGE_RADIUS)) != NULL)
+		{
+			//if (!search->takedamage)
+			//	continue;
+			// if it's a monster or player with health > 0
+			// or it's a deathmatch start point
+			// and we can see it
+			// blow up
+			if(search->classname)
+			{
+				if (   ( (!strcmp(search->classname, "info_player_deathmatch"))
+					|| (!strcmp(search->classname, "info_player_start"))
+					|| (!strcmp(search->classname, "info_player_coop"))
+					|| (!strcmp(search->classname, "misc_teleporter_dest"))
+					) 
+					&& (visible (search, self))
+				   )
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("Tesla to close to %s, removing!\n", search->classname);
+					tesla_remove (self);
+					return;
+				}
+			}
+		}
+	}
+
+	trigger = G_Spawn();
+//	if (trigger->nextthink)
+//	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("tesla_activate:  fixing nextthink\n");
+//		trigger->nextthink = 0;
+//	}
+	VectorCopy (self->s.origin, trigger->s.origin);
+	VectorSet (trigger->mins, -TESLA_DAMAGE_RADIUS, -TESLA_DAMAGE_RADIUS, self->mins[2]);
+	VectorSet (trigger->maxs, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS, TESLA_DAMAGE_RADIUS);
+	trigger->movetype = MOVETYPE_NONE;
+	trigger->solid = SOLID_TRIGGER;
+	trigger->owner = self;
+	trigger->touch = tesla_zap;
+	trigger->classname = "tesla trigger";
+	// doesn't need to be marked as a teamslave since the move code for bounce looks for teamchains
+	gi.linkentity (trigger);
+
+	VectorClear (self->s.angles);
+	// clear the owner if in deathmatch
+	if (deathmatch->value)
+		self->owner = NULL;
+	self->teamchain = trigger;
+	self->think = tesla_think_active;
+	self->nextthink = level.time + FRAMETIME;
+	self->air_finished = level.time + TESLA_TIME_TO_LIVE;
+}
+
+void tesla_think (edict_t *ent)
+{
+	if (gi.pointcontents (ent->s.origin) & (CONTENTS_SLIME|CONTENTS_LAVA))
+	{
+		tesla_remove (ent);
+		return;
+	}
+	VectorClear (ent->s.angles);
+
+	if(!(ent->s.frame))
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/teslaopen.wav"), 1, ATTN_NORM, 0); 
+
+	ent->s.frame++;
+	if(ent->s.frame > 14)
+	{
+		ent->s.frame = 14;
+		ent->think = tesla_activate;
+		ent->nextthink = level.time + 0.1;
+	}
+	else
+	{
+		if(ent->s.frame > 9)
+		{
+			if(ent->s.frame == 10)
+			{
+				if (ent->owner && ent->owner->client)
+				{
+					PlayerNoise(ent->owner, ent->s.origin, PNOISE_WEAPON);		// PGM
+				}
+				ent->s.skinnum = 1;
+			}
+			else if(ent->s.frame == 12)
+				ent->s.skinnum = 2;
+			else if(ent->s.frame == 14)
+				ent->s.skinnum = 3;
+		}
+		ent->think = tesla_think;
+		ent->nextthink = level.time + 0.1;
+	}
+}
+
+void tesla_lava (edict_t *ent, edict_t *, cplane_t *plane, csurface_t *)
+{
+	vec3_t	land_point;
+
+	if (plane->normal)
+	{
+		VectorMA (ent->s.origin, -20.0, plane->normal, land_point);
+		if (gi.pointcontents (land_point) & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			tesla_blow (ent);
+			return;
+		}
+	}
+	if (qrandom() > 0.5)
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+	else
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+}
+
+void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage_multiplier, int speed)
+{
+	edict_t	*tesla;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	tesla = G_Spawn();
+	VectorCopy (start, tesla->s.origin);
+	VectorScale (aimdir, speed, tesla->velocity);
+	VectorMA (tesla->velocity, 200 + crandom() * 10.0, up, tesla->velocity);
+	VectorMA (tesla->velocity, crandom() * 10.0, right, tesla->velocity);
+//	VectorCopy (dir, tesla->s.angles);
+	VectorClear (tesla->s.angles);
+	tesla->movetype = MOVETYPE_BOUNCE;
+	tesla->solid = SOLID_BBOX;
+	tesla->s.effects |= EF_GRENADE;
+	tesla->s.renderfx |= RF_IR_VISIBLE;
+//	VectorClear (tesla->mins);
+//	VectorClear (tesla->maxs);
+	VectorSet (tesla->mins, -12, -12, 0);
+	VectorSet (tesla->maxs, 12, 12, 20);
+	tesla->s.modelindex = gi.modelindex ("models/weapons/g_tesla/tris.md2");
+	
+	tesla->owner = self;		// PGM - we don't want it owned by self YET.
+	tesla->teammaster = self;
+
+	tesla->wait = level.time + TESLA_TIME_TO_LIVE;
+	tesla->think = tesla_think;
+	tesla->nextthink = level.time + TESLA_ACTIVATE_TIME;
+
+	// blow up on contact with lava & slime code
+	tesla->touch = tesla_lava;
+
+	if(deathmatch->value)
+		// PMM - lowered from 50 - 7/29/1998
+		tesla->health = 20;
+	else
+		tesla->health = 30;		// FIXME - change depending on skill?
+
+	tesla->takedamage = DAMAGE_YES;
+	tesla->die = tesla_die;
+	tesla->dmg = TESLA_DAMAGE*damage_multiplier;
+//	tesla->dmg = 0;
+	tesla->classname = "tesla";
+	tesla->svflags |= SVF_DAMAGEABLE;
+	tesla->clipmask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
+	tesla->flags |= FL_MECHANICAL;
+
+	gi.linkentity (tesla);
+}
+#endif
+
+// *************************
+//  HEATBEAM
+// *************************
+
+#ifdef INCLUDE_BEAMS
+static void fire_beams (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t, int damage, int kick, int te_beam, int, int mod)
+{
+	trace_t		tr;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	vec3_t		water_start, endpoint;
+	qboolean	water = false, underwater = false;
+	int			content_mask = MASK_SHOT | MASK_WATER;
+	vec3_t		beam_endpt;
+
+//	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+//	if (!(tr.fraction < 1.0))
+//	{
+	vectoangles2 (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	VectorMA (start, 8192, forward, end);
+
+	if (gi.pointcontents (start) & MASK_WATER)
+	{
+//		gi.dprintf ("Heat beam under water\n");
+		underwater = true;
+		VectorCopy (start, water_start);
+		content_mask &= ~MASK_WATER;
+	}
+
+	tr = gi.trace (start, NULL, NULL, end, self, content_mask);
+
+	// see if we hit water
+	if (tr.contents & MASK_WATER)
+	{
+		water = true;
+		VectorCopy (tr.endpos, water_start);
+
+		if (!VectorCompare (start, tr.endpos))
+		{
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_HEATBEAM_SPARKS);
+//			gi.WriteByte (50);
+			gi.WritePosition (water_start);
+			gi.WriteDir (tr.plane.normal);
+//			gi.WriteByte (8);
+//			gi.WriteShort (60);
+			gi.multicast (tr.endpos, MULTICAST_PVS);
+		}
+		// re-trace ignoring water this time
+		tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
+	}
+	VectorCopy (tr.endpos, endpoint);
+//	}
+
+	// halve the damage if target underwater
+	if (water)
+	{
+		damage = damage /2;
+	}
+
+	// send gun puff / flash
+	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+	{
+		if (tr.fraction < 1.0)
+		{
+			if (tr.ent->takedamage)
+			{
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_ENERGY, mod);
+			}
+			else
+			{
+				if ((!water) && (strncmp (tr.surface->name, "sky", 3)))
+				{
+					// This is the truncated steam entry - uses 1+1+2 extra bytes of data
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (TE_HEATBEAM_STEAM);
+//					gi.WriteByte (20);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+//					gi.WriteByte (0xe0);
+//					gi.WriteShort (60);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+
+					if (self->client)
+						PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+				}
+			}
+		}
+	}
+
+	// if went through water, determine where the end and make a bubble trail
+	if ((water) || (underwater))
+	{
+		vec3_t	pos;
+
+		VectorSubtract (tr.endpos, water_start, dir);
+		VectorNormalize (dir);
+		VectorMA (tr.endpos, -2, dir, pos);
+		if (gi.pointcontents (pos) & MASK_WATER)
+			VectorCopy (pos, tr.endpos);
+		else
+			tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+
+		VectorAdd (water_start, tr.endpos, pos);
+		VectorScale (pos, 0.5, pos);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BUBBLETRAIL2);
+//		gi.WriteByte (8);
+		gi.WritePosition (water_start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (pos, MULTICAST_PVS);
+	}
+
+	if ((!underwater) && (!water))
+	{
+		VectorCopy (tr.endpos, beam_endpt);
+	}
+	else
+	{
+		VectorCopy (endpoint, beam_endpt);
+	}
+	
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (te_beam);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (beam_endpt);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+
+}
+
+
+/*
+=================
+fire_heat
+
+Fires a single heat beam.  Zap.
+=================
+*/
+void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster)
+{
+	if (monster)
+		fire_beams (self, start, aimdir, offset, damage, kick, TE_MONSTER_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
+	else
+		fire_beams (self, start, aimdir, offset, damage, kick, TE_HEATBEAM, TE_HEATBEAM_SPARKS, MOD_HEATBEAM);
+}
+
+#endif
+
+
+// *************************
+//	BLASTER 2
+// *************************
+
+/*
+=================
+fire_blaster2
+
+Fires a single green blaster bolt.  Used by monsters, generally.
+=================
+*/
+void blaster2_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	int		mod;
+	int		damagestat;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner && self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		// the only time players will be firing blaster2 bolts will be from the 
+		// defender sphere.
+		if(self->owner->client)
+			mod = MOD_DEFENDER_SPHERE;
+		else
+			mod = MOD_BLASTER2;
+
+		if (self->owner)
+		{
+			damagestat = self->owner->takedamage;
+			self->owner->takedamage = DAMAGE_NO;
+			if (self->dmg >= 5)
+				T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
+			T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+			self->owner->takedamage = damagestat;
+		}
+		else
+		{
+			if (self->dmg >= 5)
+				T_RadiusDamage(self, self->owner, self->dmg*3, other, self->dmg_radius, 0);
+			T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+		}
+	}
+	else
+	{
+		//PMM - yeowch this will get expensive
+		if (self->dmg >= 5)
+			T_RadiusDamage(self, self->owner, self->dmg*3, self->owner, self->dmg_radius, 0);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BLASTER2);
+		gi.WritePosition (self->s.origin);
+		if (!plane)
+			gi.WriteDir (vec3_origin);
+		else
+			gi.WriteDir (plane->normal);
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+
+	G_FreeEdict (self);
+}
+
+void fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean)
+{
+	edict_t	*bolt;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn();
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles2 (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->s.effects |= effect;
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+	
+		if (effect)
+			bolt->s.effects |= EF_TRACKER;
+		bolt->dmg_radius = 128;
+		bolt->s.modelindex = gi.modelindex ("models/proj/laser2/tris.md2");
+		bolt->touch = blaster2_touch;
+
+	bolt->owner = self;
+	bolt->nextthink = level.time + 2;
+	bolt->think = G_FreeEdict;
+	bolt->dmg = damage;
+	bolt->classname = "bolt";
+ 	gi.linkentity (bolt);
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+}	
+
+// *************************
+// tracker
+// *************************
+
+/*
+void tracker_boom_think (edict_t *self)
+{
+	self->s.frame--;
+	if(self->s.frame < 0)
+		G_FreeEdict(self);
+	else
+		self->nextthink = level.time + 0.1;
+}
+
+void tracker_boom_spawn (vec3_t origin)
+{
+	edict_t *boom;
+
+	boom = G_Spawn();
+	VectorCopy (origin, boom->s.origin);
+	boom->s.modelindex = gi.modelindex ("models/items/spawngro/tris.md2");
+	boom->s.skinnum = 1;
+	boom->s.frame = 2;
+	boom->classname = "tracker boom";
+	gi.linkentity (boom);
+
+	boom->think = tracker_boom_think;
+	boom->nextthink = level.time + 0.1;
+	//PMM
+//	boom->s.renderfx |= RF_TRANSLUCENT;
+	boom->s.effects |= EF_SPHERETRANS;
+	//pmm
+}
+*/
+
+#define TRACKER_DAMAGE_FLAGS	(DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY | DAMAGE_NO_KNOCKBACK)
+#define TRACKER_IMPACT_FLAGS	(DAMAGE_NO_POWER_ARMOR | DAMAGE_ENERGY)
+
+#define TRACKER_DAMAGE_TIME		0.5		// seconds
+
+void tracker_pain_daemon_think (edict_t *self)
+{
+	static vec3_t	pain_normal = { 0, 0, 1 };
+	int				hurt;
+
+	if(!self->inuse)
+		return;
+
+	if((level.time - self->timestamp) > TRACKER_DAMAGE_TIME)
+	{
+		if(!self->enemy->client)
+			self->enemy->s.effects &= ~EF_TRACKERTRAIL;
+		G_FreeEdict (self);
+	}
+	else
+	{
+		if(self->enemy->health > 0)
+		{
+//			gi.dprintf("ouch %x\n", self);
+			T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin, pain_normal,
+						self->dmg, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
+			
+			// if we kill the player, we'll be removed.
+			if(self->inuse)
+			{
+				// if we killed a monster, gib them.
+				if (self->enemy->health < 1)
+				{
+					if(self->enemy->gib_health)
+						hurt = - self->enemy->gib_health;
+					else
+						hurt = 500;
+
+//					gi.dprintf("non-player killed. ensuring gib!  %d\n", hurt);
+					T_Damage (self->enemy, self, self->owner, vec3_origin, self->enemy->s.origin,
+								pain_normal, hurt, 0, TRACKER_DAMAGE_FLAGS, MOD_TRACKER);
+				}
+
+				if(self->enemy->client)
+					self->enemy->client->tracker_pain_framenum = level.framenum + 1;
+				else
+					self->enemy->s.effects |= EF_TRACKERTRAIL;
+				
+				self->nextthink = level.time + FRAMETIME;
+			}
+		}
+		else
+		{
+			if(!self->enemy->client)
+				self->enemy->s.effects &= ~EF_TRACKERTRAIL;
+			G_FreeEdict (self);
+		}
+	}
+}
+
+void tracker_pain_daemon_spawn (edict_t *owner, edict_t *enemy, int damage)
+{
+	edict_t	 *daemon;
+
+	if(enemy == NULL)
+		return;
+
+	daemon = G_Spawn();
+	daemon->classname = "pain daemon";
+	daemon->think = tracker_pain_daemon_think;
+	daemon->nextthink = level.time + FRAMETIME;
+	daemon->timestamp = level.time;
+	daemon->owner = owner;
+	daemon->enemy = enemy;
+	daemon->dmg = damage;
+}
+
+void tracker_explode (edict_t *self, cplane_t *plane)
+{
+	vec3_t	dir;
+
+	if(!plane)
+		VectorClear (dir);
+	else
+		VectorScale (plane->normal, 256, dir);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_TRACKER_EXPLOSION);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+//	gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/disrupthit.wav"), 1, ATTN_NORM, 0);
+//	tracker_boom_spawn(self->s.origin);
+
+	G_FreeEdict (self);
+}
+
+void tracker_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	float	damagetime;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		if((other->svflags & SVF_MONSTER) || other->client)
+		{
+			if(other->health > 0)		// knockback only for living creatures
+			{
+				// PMM - kickback was times 4 .. reduced to 3
+				// now this does no damage, just knockback
+				T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
+							/* self->dmg */ 0, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
+				
+				if (!(other->flags & (FL_FLY|FL_SWIM)))
+					other->velocity[2] += 140;
+				
+				damagetime = ((float)self->dmg)*FRAMETIME;
+				damagetime = damagetime / TRACKER_DAMAGE_TIME;
+//				gi.dprintf ("damage is %f\n", damagetime);
+
+				tracker_pain_daemon_spawn (self->owner, other, (int)damagetime);
+			}
+			else						// lots of damage (almost autogib) for dead bodies
+			{
+				T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
+							self->dmg*4, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
+			}
+		}
+		else	// full damage in one shot for inanimate objects
+		{
+			T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
+						self->dmg, (self->dmg*3), TRACKER_IMPACT_FLAGS, MOD_TRACKER);
+		}
+	}
+
+	tracker_explode (self, plane);
+	return;
+}
+
+void tracker_fly (edict_t *self)
+{
+	vec3_t	dest;
+	vec3_t	dir;
+	vec3_t	center;
+
+	if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->health < 1))
+	{
+		tracker_explode (self, NULL);
+		return;
+	}
+/*
+	VectorCopy (self->enemy->s.origin, dest);
+	if(self->enemy->client)
+		dest[2] += self->enemy->viewheight;
+*/
+	// PMM - try to hunt for center of enemy, if possible and not client
+	if(self->enemy->client)
+	{
+		VectorCopy (self->enemy->s.origin, dest);
+		dest[2] += self->enemy->viewheight;
+	}
+	// paranoia
+	else if (VectorCompare(self->enemy->absmin, vec3_origin) || VectorCompare(self->enemy->absmax, vec3_origin))
+	{
+		VectorCopy (self->enemy->s.origin, dest);
+	}
+	else
+	{
+		VectorMA (vec3_origin, 0.5, self->enemy->absmin, center);
+		VectorMA (center, 0.5, self->enemy->absmax, center);
+		VectorCopy (center, dest);
+	}
+
+	VectorSubtract (dest, self->s.origin, dir);
+	VectorNormalize (dir);
+	vectoangles2 (dir, self->s.angles);
+	VectorScale (dir, self->speed, self->velocity);
+	VectorCopy(dest, self->monsterinfo.saved_goal);
+
+	self->nextthink = level.time + 0.1;
+}
+
+void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy)
+{
+	edict_t	*bolt;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn();
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles2 (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->speed = speed;
+	bolt->s.effects = EF_TRACKER;
+	bolt->s.sound = gi.soundindex ("weapons/disrupt.wav");
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+	
+	bolt->s.modelindex = gi.modelindex ("models/proj/disintegrator/tris.md2");
+	bolt->touch = tracker_touch;
+	bolt->enemy = enemy;
+	bolt->owner = self;
+	bolt->dmg = damage;
+	bolt->classname = "tracker";
+	gi.linkentity (bolt);
+
+	if(enemy)
+	{
+		bolt->nextthink = level.time + 0.1;
+		bolt->think = tracker_fly;
+	}
+	else
+	{
+		bolt->nextthink = level.time + 10;
+		bolt->think = G_FreeEdict;
+	}
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+}	
--- /dev/null
+++ b/rogue/g_phys.c
@@ -1,0 +1,1118 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+
+
+pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
+
+onground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects 
+
+doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
+bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
+corpses are SOLID_NOT and MOVETYPE_TOSS
+crates are SOLID_BBOX and MOVETYPE_TOSS
+walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
+flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
+
+solid_edge items only clip against bsp models.
+
+*/
+
+void SV_Physics_NewToss (edict_t *ent);			// PGM
+
+
+/*
+============
+SV_TestEntityPosition
+
+============
+*/
+edict_t	*SV_TestEntityPosition (edict_t *ent)
+{
+	trace_t	trace;
+	int		mask;
+
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
+	
+	if (trace.startsolid)
+		return g_edicts;
+		
+	return NULL;
+}
+
+
+/*
+================
+SV_CheckVelocity
+================
+*/
+void SV_CheckVelocity (edict_t *ent)
+{
+	int		i;
+
+//
+// bound velocity
+//
+	for (i=0 ; i<3 ; i++)
+	{
+		if (ent->velocity[i] > sv_maxvelocity->value)
+			ent->velocity[i] = sv_maxvelocity->value;
+		else if (ent->velocity[i] < -sv_maxvelocity->value)
+			ent->velocity[i] = -sv_maxvelocity->value;
+	}
+}
+
+/*
+=============
+SV_RunThink
+
+Runs thinking code for this frame if necessary
+=============
+*/
+qboolean SV_RunThink (edict_t *ent)
+{
+	float	thinktime;
+
+	thinktime = ent->nextthink;
+	if (thinktime <= 0)
+		return true;
+	if (thinktime > level.time+0.001)
+		return true;
+	
+	ent->nextthink = 0;
+	if (!ent->think)
+		gi.error ("NULL ent->think");
+	ent->think (ent);
+
+	return false;
+}
+
+/*
+==================
+SV_Impact
+
+Two entities have touched, so run their touch functions
+==================
+*/
+void SV_Impact (edict_t *e1, trace_t *trace)
+{
+	edict_t		*e2;
+//	cplane_t	backplane;
+
+	e2 = trace->ent;
+
+	if (e1->touch && e1->solid != SOLID_NOT)
+		e1->touch (e1, e2, &trace->plane, trace->surface);
+	
+	if (e2->touch && e2->solid != SOLID_NOT)
+		e2->touch (e2, e1, NULL, NULL);
+}
+
+
+/*
+==================
+ClipVelocity
+
+Slide off of the impacting object
+returns the blocked flags (1 = floor, 2 = step / wall)
+==================
+*/
+#define	STOP_EPSILON	0.1
+
+int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
+{
+	float	backoff;
+	float	change;
+	int		i, blocked;
+	
+	blocked = 0;
+	if (normal[2] > 0)
+		blocked |= 1;		// floor
+	if (!normal[2])
+		blocked |= 2;		// step
+	
+	backoff = DotProduct (in, normal) * overbounce;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		change = normal[i]*backoff;
+		out[i] = in[i] - change;
+		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
+			out[i] = 0;
+	}
+	
+	return blocked;
+}
+
+
+/*
+============
+SV_FlyMove
+
+The basic solid body movement clip that slides along multiple planes
+Returns the clipflags if the velocity was modified (hit something solid)
+1 = floor
+2 = wall / step
+4 = dead stop
+============
+*/
+#define	MAX_CLIP_PLANES	5
+int SV_FlyMove (edict_t *ent, float time, int mask)
+{
+	edict_t		*hit;
+	int			bumpcount, numbumps;
+	vec3_t		dir;
+	float		d;
+	int			numplanes;
+	vec3_t		planes[MAX_CLIP_PLANES];
+	vec3_t		primal_velocity, original_velocity, new_velocity;
+	int			i, j;
+	trace_t		trace;
+	vec3_t		end;
+	float		time_left;
+	int			blocked;
+	
+	numbumps = 4;
+	
+	blocked = 0;
+	VectorCopy (ent->velocity, original_velocity);
+	VectorCopy (ent->velocity, primal_velocity);
+	numplanes = 0;
+	
+	time_left = time;
+
+	ent->groundentity = NULL;
+	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
+	{
+		for (i=0 ; i<3 ; i++)
+			end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
+
+		trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
+
+		if (trace.allsolid)
+		{	// entity is trapped in another solid
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		if (trace.fraction > 0)
+		{	// actually covered some distance
+			VectorCopy (trace.endpos, ent->s.origin);
+			VectorCopy (ent->velocity, original_velocity);
+			numplanes = 0;
+		}
+
+		if (trace.fraction == 1)
+			 break;		// moved the entire distance
+
+		hit = trace.ent;
+
+		if (trace.plane.normal[2] > 0.7)
+		{
+			blocked |= 1;		// floor
+			if ( hit->solid == SOLID_BSP)
+			{
+				ent->groundentity = hit;
+				ent->groundentity_linkcount = hit->linkcount;
+			}
+		}
+		if (!trace.plane.normal[2])
+		{
+			blocked |= 2;		// step
+		}
+
+//
+// run the impact function
+//
+		SV_Impact (ent, &trace);
+		if (!ent->inuse)
+			break;		// removed by the impact function
+
+		
+		time_left -= time_left * trace.fraction;
+		
+	// cliped to another plane
+		if (numplanes >= MAX_CLIP_PLANES)
+		{	// this shouldn't really happen
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		VectorCopy (trace.plane.normal, planes[numplanes]);
+		numplanes++;
+
+//
+// modify original_velocity so it parallels all of the clip planes
+//
+		for (i=0 ; i<numplanes ; i++)
+		{
+			ClipVelocity (original_velocity, planes[i], new_velocity, 1);
+
+			for (j=0 ; j<numplanes ; j++)
+				if ((j != i) && !VectorCompare (planes[i], planes[j]))
+				{
+					if (DotProduct (new_velocity, planes[j]) < 0)
+						break;	// not ok
+				}
+			if (j == numplanes)
+				break;
+		}
+		
+		if (i != numplanes)
+		{	// go along this plane
+			VectorCopy (new_velocity, ent->velocity);
+		}
+		else
+		{	// go along the crease
+			if (numplanes != 2)
+			{
+//				gi.dprintf ("clip velocity, numplanes == %i\n",numplanes);
+				VectorCopy (vec3_origin, ent->velocity);
+				return 7;
+			}
+			CrossProduct (planes[0], planes[1], dir);
+			d = DotProduct (dir, ent->velocity);
+			VectorScale (dir, d, ent->velocity);
+		}
+
+//
+// if original velocity is against the original velocity, stop dead
+// to avoid tiny occilations in sloping corners
+//
+		if (DotProduct (ent->velocity, primal_velocity) <= 0)
+		{
+			VectorCopy (vec3_origin, ent->velocity);
+			return blocked;
+		}
+	}
+
+	return blocked;
+}
+
+
+/*
+============
+SV_AddGravity
+
+============
+*/
+void SV_AddGravity (edict_t *ent)
+{
+#ifdef ROGUE_GRAVITY
+	if(ent->gravityVector[2] > 0)
+	{
+		VectorMA(ent->velocity,
+				 ent->gravity * sv_gravity->value * FRAMETIME,
+				 ent->gravityVector,
+				 ent->velocity);
+	}
+	else
+		ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+#else
+	ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+#endif
+}
+
+/*
+===============================================================================
+
+PUSHMOVE
+
+===============================================================================
+*/
+
+/*
+============
+SV_PushEntity
+
+Does not change the entities velocity at all
+============
+*/
+trace_t SV_PushEntity (edict_t *ent, vec3_t push)
+{
+	trace_t	trace;
+	vec3_t	start;
+	vec3_t	end;
+	int		mask;
+
+	VectorCopy (ent->s.origin, start);
+	VectorAdd (start, push, end);
+
+retry:
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+
+	trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask);
+	
+	VectorCopy (trace.endpos, ent->s.origin);
+	gi.linkentity (ent);
+
+	if (trace.fraction != 1.0)
+	{
+		SV_Impact (ent, &trace);
+
+		// if the pushed entity went away and the pusher is still there
+		if (!trace.ent->inuse && ent->inuse)
+		{
+			// move the pusher back and try again
+			VectorCopy (start, ent->s.origin);
+			gi.linkentity (ent);
+			goto retry;
+		}
+	}
+
+// ================
+// PGM
+	// FIXME - is this needed?
+	ent->gravity = 1.0;
+// PGM
+// ================
+
+	if (ent->inuse)
+		G_TouchTriggers (ent);
+
+	return trace;
+}					
+
+
+typedef struct
+{
+	edict_t	*ent;
+	vec3_t	origin;
+	vec3_t	angles;
+	float	deltayaw;
+} pushed_t;
+pushed_t	pushed[MAX_EDICTS], *pushed_p;
+
+edict_t	*obstacle;
+
+/*
+============
+SV_Push
+
+Objects need to be moved back on a failed push,
+otherwise riders would continue to slide.
+============
+*/
+qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
+{
+	int			i, e;
+	edict_t		*check, *block;
+	vec3_t		mins, maxs;
+	pushed_t	*p;
+	vec3_t		org, org2, move2, forward, right, up;
+
+	// clamp the move to 1/8 units, so the position will
+	// be accurate for client side prediction
+	for (i=0 ; i<3 ; i++)
+	{
+		float	temp;
+		temp = move[i]*8.0;
+		if (temp > 0.0)
+			temp += 0.5;
+		else
+			temp -= 0.5;
+		move[i] = 0.125 * (int)temp;
+	}
+
+	// find the bounding box
+	for (i=0 ; i<3 ; i++)
+	{
+		mins[i] = pusher->absmin[i] + move[i];
+		maxs[i] = pusher->absmax[i] + move[i];
+	}
+
+// we need this for pushing things later
+	VectorSubtract (vec3_origin, amove, org);
+	AngleVectors (org, forward, right, up);
+
+// save the pusher's original position
+	pushed_p->ent = pusher;
+	VectorCopy (pusher->s.origin, pushed_p->origin);
+	VectorCopy (pusher->s.angles, pushed_p->angles);
+	if (pusher->client)
+		pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
+	pushed_p++;
+
+// move the pusher to it's final position
+	VectorAdd (pusher->s.origin, move, pusher->s.origin);
+	VectorAdd (pusher->s.angles, amove, pusher->s.angles);
+	gi.linkentity (pusher);
+
+// see if any solid entities are inside the final position
+	check = g_edicts+1;
+	for (e = 1; e < globals.num_edicts; e++, check++)
+	{
+		if (!check->inuse)
+			continue;
+		if (check->movetype == MOVETYPE_PUSH
+		|| check->movetype == MOVETYPE_STOP
+		|| check->movetype == MOVETYPE_NONE
+		|| check->movetype == MOVETYPE_NOCLIP)
+			continue;
+
+		if (!check->area.prev)
+			continue;		// not linked in anywhere
+
+	// if the entity is standing on the pusher, it will definitely be moved
+		if (check->groundentity != pusher)
+		{
+			// see if the ent needs to be tested
+			if ( check->absmin[0] >= maxs[0]
+			|| check->absmin[1] >= maxs[1]
+			|| check->absmin[2] >= maxs[2]
+			|| check->absmax[0] <= mins[0]
+			|| check->absmax[1] <= mins[1]
+			|| check->absmax[2] <= mins[2] )
+				continue;
+
+			// see if the ent's bbox is inside the pusher's final position
+			if (!SV_TestEntityPosition (check))
+				continue;
+		}
+
+		if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
+		{
+			// move this entity
+			pushed_p->ent = check;
+			VectorCopy (check->s.origin, pushed_p->origin);
+			VectorCopy (check->s.angles, pushed_p->angles);
+			pushed_p++;
+
+			// try moving the contacted entity 
+			VectorAdd (check->s.origin, move, check->s.origin);
+			if (check->client)
+			{	// FIXME: doesn't rotate monsters?
+				check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
+			}
+
+			// figure movement due to the pusher's amove
+			VectorSubtract (check->s.origin, pusher->s.origin, org);
+			org2[0] = DotProduct (org, forward);
+			org2[1] = -DotProduct (org, right);
+			org2[2] = DotProduct (org, up);
+			VectorSubtract (org2, org, move2);
+			VectorAdd (check->s.origin, move2, check->s.origin);
+
+			// may have pushed them off an edge
+			if (check->groundentity != pusher)
+				check->groundentity = NULL;
+
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{	// pushed ok
+				gi.linkentity (check);
+				// impact?
+				continue;
+			}
+
+			// if it is ok to leave in the old position, do it
+			// this is only relevent for riding entities, not pushed
+			// FIXME: this doesn't acount for rotation
+			VectorSubtract (check->s.origin, move, check->s.origin);
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{
+				pushed_p--;
+				continue;
+			}
+		}
+		
+		// save off the obstacle so we can call the block function
+		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.origin);
+			VectorCopy (p->angles, p->ent->s.angles);
+			if (p->ent->client)
+			{
+				p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
+			}
+			gi.linkentity (p->ent);
+		}
+		return false;
+	}
+
+//FIXME: is there a better way to handle this?
+	// see if anything we moved has touched a trigger
+	for (p=pushed_p-1 ; p>=pushed ; p--)
+		G_TouchTriggers (p->ent);
+
+	return true;
+}
+
+/*
+================
+SV_Physics_Pusher
+
+Bmodel objects don't interact with each other, but
+push all box objects
+================
+*/
+void SV_Physics_Pusher (edict_t *ent)
+{
+	vec3_t		move, amove;
+	edict_t		*part, *mv;
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	// 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
+//retry:
+	pushed_p = pushed;
+	for (part = ent ; part ; part=part->teamchain)
+	{
+		if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
+			part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
+			)
+		{	// object is moving
+			VectorScale (part->velocity, FRAMETIME, move);
+			VectorScale (part->avelocity, FRAMETIME, amove);
+
+			if (!SV_Push (part, move, amove))
+				break;	// move was blocked
+		}
+	}
+	if (pushed_p > &pushed[MAX_EDICTS])
+		gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
+
+	if (part)
+	{
+		// the move failed, bump all nextthink times and back out moves
+		for (mv = ent ; mv ; mv=mv->teamchain)
+		{
+			if (mv->nextthink > 0)
+				mv->nextthink += FRAMETIME;
+		}
+
+		// if the pusher has a "blocked" function, call it
+		// otherwise, just stay in place until the obstacle is gone
+		if (part->blocked)
+			part->blocked (part, obstacle);
+/*
+		// if the pushed entity went away and the pusher is still there
+		if (!obstacle->inuse && part->inuse)
+			goto retry;
+*/
+	}
+	else
+	{
+		// the move succeeded, so call all think functions
+		for (part = ent ; part ; part=part->teamchain)
+		{
+			// prevent entities that are on trains that have gone away from thinking!
+			if (part->inuse)
+				SV_RunThink (part);
+		}
+	}
+}
+
+//==================================================================
+
+/*
+=============
+SV_Physics_None
+
+Non moving objects can only think
+=============
+*/
+void SV_Physics_None (edict_t *ent)
+{
+// regular thinking
+	SV_RunThink (ent);
+}
+
+/*
+=============
+SV_Physics_Noclip
+
+A moving object that doesn't obey physics
+=============
+*/
+void SV_Physics_Noclip (edict_t *ent)
+{
+// regular thinking
+	if (!SV_RunThink (ent))
+		return;
+	
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
+
+	gi.linkentity (ent);
+}
+
+/*
+==============================================================================
+
+TOSS / BOUNCE
+
+==============================================================================
+*/
+
+/*
+=============
+SV_Physics_Toss
+
+Toss, bounce, and fly movement.  When onground, do nothing.
+=============
+*/
+void SV_Physics_Toss (edict_t *ent)
+{
+	trace_t		trace;
+	vec3_t		move;
+	float		backoff;
+	edict_t		*slave;
+	qboolean	wasinwater;
+	qboolean	isinwater;
+	vec3_t		old_origin;
+
+// regular thinking
+	SV_RunThink (ent);
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	if (ent->velocity[2] > 0)
+		ent->groundentity = NULL;
+
+// check for the groundentity going away
+	if (ent->groundentity)
+		if (!ent->groundentity->inuse)
+			ent->groundentity = NULL;
+
+// if onground, return without moving
+	if ( ent->groundentity && ent->gravity > 0.0)		// PGM - gravity hack
+		return;
+
+	VectorCopy (ent->s.origin, old_origin);
+
+	SV_CheckVelocity (ent);
+
+// add gravity
+	if (ent->movetype != MOVETYPE_FLY
+	&& ent->movetype != MOVETYPE_FLYMISSILE)
+		SV_AddGravity (ent);
+
+// move angles
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+
+// move origin
+	VectorScale (ent->velocity, FRAMETIME, move);
+	trace = SV_PushEntity (ent, move);
+	if (!ent->inuse)
+		return;
+
+	if (trace.fraction < 1)
+	{
+		if (ent->movetype == MOVETYPE_BOUNCE)
+			backoff = 1.5;
+		else
+			backoff = 1;
+
+		ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff);
+
+	// stop if on ground
+		if (trace.plane.normal[2] > 0.7)
+		{		
+			if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE )
+			{
+				ent->groundentity = trace.ent;
+				ent->groundentity_linkcount = trace.ent->linkcount;
+				VectorCopy (vec3_origin, ent->velocity);
+				VectorCopy (vec3_origin, ent->avelocity);
+			}
+		}
+
+//		if (ent->touch)
+//			ent->touch (ent, trace.ent, &trace.plane, trace.surface);
+	}
+	
+// check for water transition
+	wasinwater = (ent->watertype & MASK_WATER);
+	ent->watertype = gi.pointcontents (ent->s.origin);
+	isinwater = ent->watertype & MASK_WATER;
+
+	if (isinwater)
+		ent->waterlevel = 1;
+	else
+		ent->waterlevel = 0;
+
+	if (!wasinwater && isinwater)
+		gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+	else if (wasinwater && !isinwater)
+		gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+
+// move teamslaves
+	for (slave = ent->teamchain; slave; slave = slave->teamchain)
+	{
+		VectorCopy (ent->s.origin, slave->s.origin);
+		gi.linkentity (slave);
+	}
+}
+
+/*
+===============================================================================
+
+STEPPING MOVEMENT
+
+===============================================================================
+*/
+
+/*
+=============
+SV_Physics_Step
+
+Monsters freefall when they don't have a ground entity, otherwise
+all movement is done with discrete steps.
+
+This is also used for objects that have become still on the ground, but
+will fall if the floor is pulled out from under them.
+FIXME: is this true?
+=============
+*/
+
+//FIXME: hacked in for E3 demo
+//#define	sv_stopspeed		100
+#define sv_friction			6
+#define sv_waterfriction	1
+
+void SV_AddRotationalFriction (edict_t *ent)
+{
+	int		n;
+	float	adjustment;
+
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	adjustment = FRAMETIME * sv_stopspeed->value * sv_friction;		//PGM now a cvar
+	for (n = 0; n < 3; n++)
+	{
+		if (ent->avelocity[n] > 0)
+		{
+			ent->avelocity[n] -= adjustment;
+			if (ent->avelocity[n] < 0)
+				ent->avelocity[n] = 0;
+		}
+		else
+		{
+			ent->avelocity[n] += adjustment;
+			if (ent->avelocity[n] > 0)
+				ent->avelocity[n] = 0;
+		}
+	}
+}
+
+void SV_Physics_Step (edict_t *ent)
+{
+	qboolean	wasonground;
+	qboolean	hitsound = false;
+	float		*vel;
+	float		speed, newspeed, control;
+	float		friction;
+	edict_t		*groundentity;
+	int			mask;
+
+	// airborn monsters should always check for ground
+	if (!ent->groundentity)
+		M_CheckGround (ent);
+
+	groundentity = ent->groundentity;
+
+	SV_CheckVelocity (ent);
+
+	if (groundentity)
+		wasonground = true;
+	else
+		wasonground = false;
+		
+	if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+		SV_AddRotationalFriction (ent);
+
+	// add gravity except:
+	//   flying monsters
+	//   swimming monsters who are in the water
+	if (! wasonground)
+		if (!(ent->flags & FL_FLY))
+			if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
+			{
+				if (ent->velocity[2] < sv_gravity->value*-0.1)
+					hitsound = true;
+				if (ent->waterlevel == 0)
+					SV_AddGravity (ent);
+			}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+		friction = sv_friction/3;
+		newspeed = speed - (FRAMETIME * control * friction);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+		newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
+	{
+		// apply friction
+		// let dead monsters who aren't completely onground slide
+		if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY)))
+			if (!(ent->health <= 0.0 && !M_CheckBottom(ent)))
+			{
+				vel = ent->velocity;
+				speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
+				if (speed)
+				{
+					friction = sv_friction;
+
+					control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed;
+					newspeed = speed - FRAMETIME*control*friction;
+
+					if (newspeed < 0)
+						newspeed = 0;
+					newspeed /= speed;
+
+					vel[0] *= newspeed;
+					vel[1] *= newspeed;
+				}
+			}
+
+		if (ent->svflags & SVF_MONSTER)
+			mask = MASK_MONSTERSOLID;
+		else
+			mask = MASK_SOLID;
+		SV_FlyMove (ent, FRAMETIME, mask);
+
+		gi.linkentity (ent);
+
+// ========
+// PGM - reset this every time they move. 
+//       G_touchtriggers will set it back if appropriate
+		ent->gravity = 1.0;
+// ========
+		
+		G_TouchTriggers (ent);
+		if (!ent->inuse)
+			return;
+
+		if (ent->groundentity)
+			if (!wasonground)
+				if (hitsound)
+					gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
+	}
+
+	if(!ent->inuse)			// PGM g_touchtrigger free problem
+		return;
+
+// regular thinking
+	SV_RunThink (ent);
+}
+
+//============================================================================
+/*
+================
+G_RunEntity
+
+================
+*/
+void G_RunEntity (edict_t *ent)
+{
+//PGM
+	trace_t	trace;
+	vec3_t	previous_origin;
+
+	if(ent->movetype == MOVETYPE_STEP)
+		VectorCopy(ent->s.origin, previous_origin);
+//PGM
+
+	if (ent->prethink)
+		ent->prethink (ent);
+
+	switch ( (int)ent->movetype)
+	{
+		case MOVETYPE_PUSH:
+		case MOVETYPE_STOP:
+			SV_Physics_Pusher (ent);
+			break;
+		case MOVETYPE_NONE:
+			SV_Physics_None (ent);
+			break;
+		case MOVETYPE_NOCLIP:
+			SV_Physics_Noclip (ent);
+			break;
+		case MOVETYPE_STEP:
+			SV_Physics_Step (ent);
+			break;
+		case MOVETYPE_TOSS:
+		case MOVETYPE_BOUNCE:
+		case MOVETYPE_FLY:
+		case MOVETYPE_FLYMISSILE:
+			SV_Physics_Toss (ent);
+			break;
+		case MOVETYPE_NEWTOSS:
+			SV_Physics_NewToss (ent);
+			break;
+		default:
+			gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype);			
+	}
+
+//PGM
+	if(ent->movetype == MOVETYPE_STEP)
+	{
+		// if we moved, check and fix origin if needed
+		if (!VectorCompare(ent->s.origin, previous_origin))
+		{
+			trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, previous_origin, ent, MASK_MONSTERSOLID);
+			if(trace.allsolid || trace.startsolid)
+				VectorCopy (previous_origin, ent->s.origin);
+		}
+	}
+//PGM
+}
+
+//============
+//ROGUE
+/*
+=============
+SV_Physics_NewToss
+
+Toss, bounce, and fly movement. When on ground and no velocity, do nothing. With velocity,
+slide.
+=============
+*/
+void SV_Physics_NewToss (edict_t *ent)
+{
+	trace_t		trace;
+	vec3_t		move;
+//	float		backoff;
+	edict_t		*slave;
+	qboolean	wasinwater;
+	qboolean	isinwater;
+	float		speed, newspeed;
+	vec3_t		old_origin;
+//	float		firstmove;
+//	int			mask;
+
+	// regular thinking
+	SV_RunThink (ent);
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+	
+	//wasinwater = ent->waterlevel;
+
+	// find out what we're sitting on.
+	VectorCopy (ent->s.origin, move);
+	move[2] -= 0.25;
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, move, ent, ent->clipmask);
+	if(ent->groundentity && ent->groundentity->inuse)
+		ent->groundentity = trace.ent;
+	else
+		ent->groundentity = NULL;
+
+	// if we're sitting on something flat and have no velocity of our own, return.
+	if (ent->groundentity && (trace.plane.normal[2] == 1.0) && 
+		!ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2])
+	{
+		return;
+	}
+
+	// store the old origin
+	VectorCopy (ent->s.origin, old_origin);
+
+	SV_CheckVelocity (ent);
+
+	// add gravity
+	SV_AddGravity (ent);
+
+	if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+		SV_AddRotationalFriction (ent);
+
+	// add friction
+	speed = VectorLength(ent->velocity);
+	if(ent->waterlevel)				// friction for water movement
+	{
+		newspeed = speed - (sv_waterfriction * 6 * ent->waterlevel);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		VectorScale (ent->velocity, newspeed, ent->velocity);
+	}
+	else if (!ent->groundentity)	// friction for air movement
+	{
+		newspeed = speed - ((sv_friction));
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		VectorScale (ent->velocity, newspeed, ent->velocity);
+	}
+	else	// use ground friction
+	{
+		newspeed = speed - (sv_friction * 6);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		VectorScale (ent->velocity, newspeed, ent->velocity);
+	}
+
+	SV_FlyMove (ent, FRAMETIME, ent->clipmask);
+	gi.linkentity (ent);
+
+	G_TouchTriggers (ent);
+
+// check for water transition
+	wasinwater = (ent->watertype & MASK_WATER);
+	ent->watertype = gi.pointcontents (ent->s.origin);
+	isinwater = ent->watertype & MASK_WATER;
+
+	if (isinwater)
+		ent->waterlevel = 1;
+	else
+		ent->waterlevel = 0;
+
+	if (!wasinwater && isinwater)
+		gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+	else if (wasinwater && !isinwater)
+		gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+
+// move teamslaves
+	for (slave = ent->teamchain; slave; slave = slave->teamchain)
+	{
+		VectorCopy (ent->s.origin, slave->s.origin);
+		gi.linkentity (slave);
+	}
+}
+
+//ROGUE
+//============
--- /dev/null
+++ b/rogue/g_save.c
@@ -1,0 +1,801 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define Function(f) {#f, f}
+
+mmove_t mmove_reloc;
+
+field_t fields[] = {
+	{"classname", FOFS(classname), F_LSTRING},
+	{"model", FOFS(model), F_LSTRING},
+	{"spawnflags", FOFS(spawnflags), F_INT},
+	{"speed", FOFS(speed), F_FLOAT},
+	{"accel", FOFS(accel), F_FLOAT},
+	{"decel", FOFS(decel), F_FLOAT},
+	{"target", FOFS(target), F_LSTRING},
+	{"targetname", FOFS(targetname), F_LSTRING},
+	{"pathtarget", FOFS(pathtarget), F_LSTRING},
+	{"deathtarget", FOFS(deathtarget), F_LSTRING},
+	{"killtarget", FOFS(killtarget), F_LSTRING},
+	{"combattarget", FOFS(combattarget), F_LSTRING},
+	{"message", FOFS(message), F_LSTRING},
+	{"team", FOFS(team), F_LSTRING},
+	{"wait", FOFS(wait), F_FLOAT},
+	{"delay", FOFS(delay), F_FLOAT},
+	{"random", FOFS(random), F_FLOAT},
+	{"move_origin", FOFS(move_origin), F_VECTOR},
+	{"move_angles", FOFS(move_angles), F_VECTOR},
+	{"style", FOFS(style), F_INT},
+	{"count", FOFS(count), F_INT},
+	{"health", FOFS(health), F_INT},
+	{"sounds", FOFS(sounds), F_INT},
+	{"light", 0, F_IGNORE},
+	{"dmg", FOFS(dmg), F_INT},
+	{"mass", FOFS(mass), F_INT},
+	{"volume", FOFS(volume), F_FLOAT},
+	{"attenuation", FOFS(attenuation), F_FLOAT},
+	{"map", FOFS(map), F_LSTRING},
+	{"origin", FOFS(s.origin), F_VECTOR},
+	{"angles", FOFS(s.angles), F_VECTOR},
+	{"angle", FOFS(s.angles), F_ANGLEHACK},
+
+	{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+	{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+	{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+	{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+	{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+	{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+	{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+	{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+	{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+	{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+	{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+	{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+	{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+
+	{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
+	{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
+	{"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
+	{"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
+	{"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
+	{"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
+	{"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
+
+	{"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
+	{"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
+	{"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
+	{"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
+	{"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
+	{"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
+	{"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
+	{"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
+	{"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
+	{"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
+	{"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
+
+	{"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
+
+	// temp spawn vars -- only valid when the spawn function is called
+	{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+	{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+	{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+	{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+	{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+	{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+
+//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves
+	{"item", FOFS(item), F_ITEM},
+
+	{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+	{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+	{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+	{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+	{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+
+	// ROGUE
+	{"bad_area", FOFS(bad_area), F_EDICT},
+	// while the hint_path stuff could be reassembled on the fly, no reason to be different
+	{"hint_chain", FOFS(hint_chain), F_EDICT},
+	{"monster_hint_chain", FOFS(monster_hint_chain), F_EDICT},
+	{"target_hint_chain", FOFS(target_hint_chain), F_EDICT},
+	//
+	{"goal_hint", FOFS(monsterinfo.goal_hint), F_EDICT},
+	{"badMedic1", FOFS(monsterinfo.badMedic1), F_EDICT},
+	{"badMedic2", FOFS(monsterinfo.badMedic2), F_EDICT},
+	{"last_player_enemy", FOFS(monsterinfo.last_player_enemy), F_EDICT},
+	{"commander", FOFS(monsterinfo.commander), F_EDICT},
+	{"blocked", FOFS(monsterinfo.blocked), F_MMOVE, FFL_NOSPAWN},
+	{"duck", FOFS(monsterinfo.duck), F_MMOVE, FFL_NOSPAWN},
+	{"unduck", FOFS(monsterinfo.unduck), F_MMOVE, FFL_NOSPAWN},
+	{"sidestep", FOFS(monsterinfo.sidestep), F_MMOVE, FFL_NOSPAWN},
+	// ROGUE	
+
+	{0, 0, 0, 0}
+
+};
+
+field_t		levelfields[] =
+{
+	{"changemap", LLOFS(changemap), F_LSTRING},
+                   
+	{"sight_client", LLOFS(sight_client), F_EDICT},
+	{"sight_entity", LLOFS(sight_entity), F_EDICT},
+	{"sound_entity", LLOFS(sound_entity), F_EDICT},
+	{"sound2_entity", LLOFS(sound2_entity), F_EDICT},
+
+	// ROGUE
+	{"disguise_violator", LLOFS(disguise_violator), F_EDICT},
+	// ROGUE
+
+	{NULL, 0, F_INT}
+};
+
+field_t		clientfields[] =
+{
+	{"pers.weapon", CLOFS(pers.weapon), F_ITEM},
+	{"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
+	{"newweapon", CLOFS(newweapon), F_ITEM},
+	// ROGUE
+	{"owned_sphere", CLOFS(owned_sphere), F_EDICT},
+	// ROGUE
+
+	{NULL, 0, F_INT}
+};
+
+/*
+============
+InitGame
+
+This will be called when the dll is first loaded, which
+only happens when a new game is started or a save game
+is loaded.
+============
+*/
+void InitGame (void)
+{
+	gi.dprintf ("==== InitGame ====\n");
+
+	gun_x = gi.cvar ("gun_x", "0", 0);
+	gun_y = gi.cvar ("gun_y", "0", 0);
+	gun_z = gi.cvar ("gun_z", "0", 0);
+
+	//FIXME: sv_ prefix is wrong for these
+	sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+	sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+	sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+	sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+
+	sv_stopspeed = gi.cvar ("sv_stopspeed", "100", 0);		// PGM - was #define in g_phys.c
+
+//ROGUE
+	g_showlogic = gi.cvar ("g_showlogic", "0", 0);
+	huntercam = gi.cvar ("huntercam", "1", CVAR_SERVERINFO|CVAR_LATCH);
+	strong_mines = gi.cvar ("strong_mines", "0", 0);
+	randomrespawn = gi.cvar ("randomrespawn", "0", 0);
+//ROGUE
+
+	// noset vars
+	dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+	// latched vars
+	sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+	gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+	gi.cvar ("gamedate", "Aug 31 1998" , CVAR_SERVERINFO | CVAR_LATCH);
+
+	maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+	maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
+	deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+	coop = gi.cvar ("coop", "0", CVAR_LATCH);
+	skill = gi.cvar ("skill", "1", CVAR_LATCH);
+	maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+	gamerules = gi.cvar ("gamerules", "0", CVAR_LATCH);			//PGM
+
+	// change anytime vars
+	dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+	fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+	timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+	password = gi.cvar ("password", "", CVAR_USERINFO);
+	spectator_password = gi.cvar ("spectator_password", "", CVAR_USERINFO);
+	filterban = gi.cvar ("filterban", "1", 0);
+
+	g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+
+	run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+	run_roll = gi.cvar ("run_roll", "0.005", 0);
+	bob_up  = gi.cvar ("bob_up", "0.005", 0);
+	bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+	bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+	// flood control
+	flood_msgs = gi.cvar ("flood_msgs", "4", 0);
+	flood_persecond = gi.cvar ("flood_persecond", "4", 0);
+	flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
+
+	// dm map list
+	sv_maplist = gi.cvar ("sv_maplist", "", 0);
+
+	// items
+	InitItems ();
+
+	Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "");
+
+	Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "");
+
+	// initialize all entities for this game
+	game.maxentities = maxentities->value;
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+	globals.max_edicts = game.maxentities;
+
+	// initialize all clients for this game
+	game.maxclients = maxclients->value;
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	globals.num_edicts = game.maxclients+1;
+
+//======
+//ROGUE
+	if(gamerules)
+	{
+		InitGameRules();	// if there are game rules to set up, do so now.
+	}
+//ROGUE
+//======
+}
+
+//=========================================================
+
+void WriteField1 (FILE *, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+	case F_GSTRING:
+		if ( *(char **)p )
+			len = strlen(*(char **)p) + 1;
+		else
+			len = 0;
+		*(int *)p = len;
+		break;
+	case F_EDICT:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(edict_t **)p - g_edicts;
+		*(int *)p = index;
+		break;
+	case F_CLIENT:
+		if ( *(gclient_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gclient_t **)p - game.clients;
+		*(int *)p = index;
+		break;
+	case F_ITEM:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gitem_t **)p - itemlist;
+		*(int *)p = index;
+		break;
+
+	//relative to code segment
+	case F_FUNCTION:
+		if (*(byte **)p == NULL)
+			index = 0;
+		else
+			index = *(byte **)p - ((byte *)InitGame);
+		*(int *)p = index;
+		break;
+
+	//relative to data segment
+	case F_MMOVE:
+		if (*(byte **)p == NULL)
+			index = 0;
+		else
+			index = *(byte **)p - (byte *)&mmove_reloc;
+		*(int *)p = index;
+		break;
+
+	default:
+		gi.error ("WriteEdict: unknown field type");
+	}
+}
+
+
+void WriteField2 (FILE *f, field_t *field, byte *base)
+{
+	int			len;
+	void		*p;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_LSTRING:
+		if ( *(char **)p )
+		{
+			len = strlen(*(char **)p) + 1;
+			fwrite (*(char **)p, len, 1, f);
+		}
+		break;
+	}
+}
+
+void ReadField (FILE *f, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+		len = *(int *)p;
+		if (!len)
+			*(char **)p = NULL;
+		else
+		{
+			*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
+			fread (*(char **)p, len, 1, f);
+		}
+		break;
+	case F_EDICT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(edict_t **)p = NULL;
+		else
+			*(edict_t **)p = &g_edicts[index];
+		break;
+	case F_CLIENT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gclient_t **)p = NULL;
+		else
+			*(gclient_t **)p = &game.clients[index];
+		break;
+	case F_ITEM:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gitem_t **)p = NULL;
+		else
+			*(gitem_t **)p = &itemlist[index];
+		break;
+
+	//relative to code segment
+	case F_FUNCTION:
+		index = *(int *)p;
+		if ( index == 0 )
+			*(byte **)p = NULL;
+		else
+			*(byte **)p = ((byte *)InitGame) + index;
+		break;
+
+	//relative to data segment
+	case F_MMOVE:
+		index = *(int *)p;
+		if (index == 0)
+			*(byte **)p = NULL;
+		else
+			*(byte **)p = (byte *)&mmove_reloc + index;
+		break;
+
+	default:
+		gi.error ("ReadEdict: unknown field type");
+	}
+}
+
+//=========================================================
+
+/*
+==============
+WriteClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+	gclient_t	temp;
+	
+	// all of the ints, floats, and vectors stay as they are
+	temp = *client;
+
+	// change the pointers to lengths or indexes
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)client);
+	}
+}
+
+/*
+==============
+ReadClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+
+	fread (client, sizeof(*client), 1, f);
+
+	for (field=clientfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)client);
+	}
+}
+
+/*
+============
+WriteGame
+
+This will be called whenever the game goes to a new level,
+and when the user explicitly saves the game.
+
+Game information include cross level data, like multi level
+triggers, help computer info, and all client states.
+
+A single player death will automatically restore from the
+last save position.
+============
+*/
+void WriteGame (char *filename, qboolean autosave)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	if (!autosave)
+		SaveClientData ();
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	memset (str, 0, sizeof(str));
+	strcpy (str, "Aug 31 1998");
+	fwrite (str, sizeof(str), 1, f);
+
+	game.autosaved = autosave;
+	fwrite (&game, sizeof(game), 1, f);
+	game.autosaved = false;
+
+	for (i=0 ; i<game.maxclients ; i++)
+		WriteClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+void ReadGame (char *filename)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	gi.FreeTags (TAG_GAME);
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	fread (str, sizeof(str), 1, f);
+	if (strcmp (str, "Aug 31 1998"))
+	{
+		fclose (f);
+		gi.error ("Savegame from an older version.\n");
+	}
+
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+
+	fread (&game, sizeof(game), 1, f);
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	for (i=0 ; i<game.maxclients ; i++)
+		ReadClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+//==========================================================
+
+
+/*
+==============
+WriteEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+	edict_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = *ent;
+
+	// change the pointers to lengths or indexes
+	for (field=fields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=fields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)ent);
+	}
+
+}
+
+/*
+==============
+WriteLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteLevelLocals (FILE *f)
+{
+	field_t		*field;
+	level_locals_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = level;
+
+	// change the pointers to lengths or indexes
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)&level);
+	}
+}
+
+
+/*
+==============
+ReadEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+
+	fread (ent, sizeof(*ent), 1, f);
+
+	for (field=fields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)ent);
+	}
+}
+
+/*
+==============
+ReadLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadLevelLocals (FILE *f)
+{
+	field_t		*field;
+
+	fread (&level, sizeof(level), 1, f);
+
+	for (field=levelfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)&level);
+	}
+}
+
+/*
+=================
+WriteLevel
+
+=================
+*/
+void WriteLevel (char *filename)
+{
+	int		i;
+	edict_t	*ent;
+	FILE	*f;
+	void	*base;
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// write out edict size for checking
+	i = sizeof(edict_t);
+	fwrite (&i, sizeof(i), 1, f);
+
+	// write out a function pointer for checking
+	base = (void *)InitGame;
+	fwrite (&base, sizeof(base), 1, f);
+
+	// write out level_locals_t
+	WriteLevelLocals (f);
+
+	// write out all the entities
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+		if (!ent->inuse)
+			continue;
+		fwrite (&i, sizeof(i), 1, f);
+		WriteEdict (f, ent);
+	}
+	i = -1;
+	fwrite (&i, sizeof(i), 1, f);
+
+	fclose (f);
+}
+
+
+/*
+=================
+ReadLevel
+
+SpawnEntities will already have been called on the
+level the same way it was when the level was saved.
+
+That is necessary to get the baselines
+set up identically.
+
+The server will have cleared all of the world links before
+calling ReadLevel.
+
+No clients are connected yet.
+=================
+*/
+void ReadLevel (char *filename)
+{
+	int		entnum;
+	FILE	*f;
+	int		i;
+	void	*base;
+	edict_t	*ent;
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// free any dynamic memory allocated by loading the level
+	// base state
+	gi.FreeTags (TAG_LEVEL);
+
+	// wipe all the entities
+	memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
+	globals.num_edicts = maxclients->value+1;
+
+	// check edict size
+	fread (&i, sizeof(i), 1, f);
+	if (i != sizeof(edict_t))
+	{
+		fclose (f);
+		gi.error ("ReadLevel: mismatched edict size");
+	}
+
+	// check function pointer base address
+	fread (&base, sizeof(base), 1, f);
+#ifdef _WIN32
+	if (base != (void *)InitGame)
+	{
+		fclose (f);
+		gi.error ("ReadLevel: function pointers have moved");
+	}
+#else
+	gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
+#endif
+
+	// load the level locals
+	ReadLevelLocals (f);
+
+	// load all the entities
+	while (1)
+	{
+		if (fread (&entnum, sizeof(entnum), 1, f) != 1)
+		{
+			fclose (f);
+			gi.error ("ReadLevel: failed to read entnum");
+		}
+		if (entnum == -1)
+			break;
+		if (entnum >= globals.num_edicts)
+			globals.num_edicts = entnum+1;
+
+		ent = &g_edicts[entnum];
+		ReadEdict (f, ent);
+
+		// let the server rebuild world links for this ent
+		memset (&ent->area, 0, sizeof(ent->area));
+		gi.linkentity (ent);
+	}
+
+	fclose (f);
+
+	// PMM - rebuild the hint path chains
+//	InitHintPaths();
+	// pmm
+
+	// mark all clients as unconnected
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = &g_edicts[i+1];
+		ent->client = game.clients + i;
+		ent->client->pers.connected = false;
+	}
+
+	// do any load time things at this point
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+
+		if (!ent->inuse)
+			continue;
+
+		// fire any cross-level triggers
+		if (ent->classname)
+			if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+				ent->nextthink = level.time + ent->delay;
+	}
+}
--- /dev/null
+++ b/rogue/g_spawn.c
@@ -1,0 +1,1771 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+typedef struct
+{
+	char	*name;
+	void	(*spawn)(edict_t *ent);
+} spawn_t;
+
+
+void SP_item_health (edict_t *self);
+void SP_item_health_small (edict_t *self);
+void SP_item_health_large (edict_t *self);
+void SP_item_health_mega (edict_t *self);
+
+void SP_info_player_start (edict_t *ent);
+void SP_info_player_deathmatch (edict_t *ent);
+void SP_info_player_coop (edict_t *ent);
+void SP_info_player_intermission (edict_t *ent);
+
+void SP_func_plat (edict_t *ent);
+void SP_func_rotating (edict_t *ent);
+void SP_func_button (edict_t *ent);
+void SP_func_door (edict_t *ent);
+void SP_func_door_secret (edict_t *ent);
+void SP_func_door_rotating (edict_t *ent);
+void SP_func_water (edict_t *ent);
+void SP_func_train (edict_t *ent);
+void SP_func_conveyor (edict_t *self);
+void SP_func_wall (edict_t *self);
+void SP_func_object (edict_t *self);
+void SP_func_explosive (edict_t *self);
+void SP_func_timer (edict_t *self);
+void SP_func_areaportal (edict_t *ent);
+void SP_func_clock (edict_t *ent);
+void SP_func_killbox (edict_t *ent);
+
+void SP_trigger_always (edict_t *ent);
+void SP_trigger_once (edict_t *ent);
+void SP_trigger_multiple (edict_t *ent);
+void SP_trigger_relay (edict_t *ent);
+void SP_trigger_push (edict_t *ent);
+void SP_trigger_hurt (edict_t *ent);
+void SP_trigger_key (edict_t *ent);
+void SP_trigger_counter (edict_t *ent);
+void SP_trigger_elevator (edict_t *ent);
+void SP_trigger_gravity (edict_t *ent);
+void SP_trigger_monsterjump (edict_t *ent);
+
+void SP_target_temp_entity (edict_t *ent);
+void SP_target_speaker (edict_t *ent);
+void SP_target_explosion (edict_t *ent);
+void SP_target_changelevel (edict_t *ent);
+void SP_target_secret (edict_t *ent);
+void SP_target_goal (edict_t *ent);
+void SP_target_splash (edict_t *ent);
+void SP_target_spawner (edict_t *ent);
+void SP_target_blaster (edict_t *ent);
+void SP_target_crosslevel_trigger (edict_t *ent);
+void SP_target_crosslevel_target (edict_t *ent);
+void SP_target_laser (edict_t *self);
+void SP_target_help (edict_t *ent);
+void SP_target_actor (edict_t *ent);
+void SP_target_lightramp (edict_t *self);
+void SP_target_earthquake (edict_t *ent);
+void SP_target_character (edict_t *ent);
+void SP_target_string (edict_t *ent);
+
+void SP_worldspawn (edict_t *ent);
+void SP_viewthing (edict_t *ent);
+
+void SP_light (edict_t *self);
+void SP_light_mine1 (edict_t *ent);
+void SP_light_mine2 (edict_t *ent);
+void SP_info_null (edict_t *self);
+void SP_info_notnull (edict_t *self);
+void SP_path_corner (edict_t *self);
+void SP_point_combat (edict_t *self);
+
+void SP_misc_explobox (edict_t *self);
+void SP_misc_banner (edict_t *self);
+void SP_misc_satellite_dish (edict_t *self);
+void SP_misc_actor (edict_t *self);
+void SP_misc_gib_arm (edict_t *self);
+void SP_misc_gib_leg (edict_t *self);
+void SP_misc_gib_head (edict_t *self);
+void SP_misc_insane (edict_t *self);
+void SP_misc_deadsoldier (edict_t *self);
+void SP_misc_viper (edict_t *self);
+void SP_misc_viper_bomb (edict_t *self);
+void SP_misc_bigviper (edict_t *self);
+void SP_misc_strogg_ship (edict_t *self);
+void SP_misc_teleporter (edict_t *self);
+void SP_misc_teleporter_dest (edict_t *self);
+void SP_misc_blackhole (edict_t *self);
+void SP_misc_eastertank (edict_t *self);
+void SP_misc_easterchick (edict_t *self);
+void SP_misc_easterchick2 (edict_t *self);
+
+void SP_monster_berserk (edict_t *self);
+void SP_monster_gladiator (edict_t *self);
+void SP_monster_gunner (edict_t *self);
+void SP_monster_infantry (edict_t *self);
+void SP_monster_soldier_light (edict_t *self);
+void SP_monster_soldier (edict_t *self);
+void SP_monster_soldier_ss (edict_t *self);
+void SP_monster_tank (edict_t *self);
+void SP_monster_medic (edict_t *self);
+void SP_monster_flipper (edict_t *self);
+void SP_monster_chick (edict_t *self);
+void SP_monster_parasite (edict_t *self);
+void SP_monster_flyer (edict_t *self);
+void SP_monster_brain (edict_t *self);
+void SP_monster_floater (edict_t *self);
+void SP_monster_hover (edict_t *self);
+void SP_monster_mutant (edict_t *self);
+void SP_monster_supertank (edict_t *self);
+void SP_monster_boss2 (edict_t *self);
+void SP_monster_jorg (edict_t *self);
+void SP_monster_boss3_stand (edict_t *self);
+
+void SP_monster_commander_body (edict_t *self);
+
+void SP_turret_breach (edict_t *self);
+void SP_turret_base (edict_t *self);
+void SP_turret_driver (edict_t *self);
+
+//===========
+//ROGUE
+void SP_func_plat2 (edict_t *ent);
+void SP_func_door_secret2(edict_t *ent);
+void SP_func_force_wall(edict_t *ent);
+void SP_info_player_coop_lava (edict_t *self);
+void SP_info_teleport_destination (edict_t *self);
+void SP_trigger_teleport (edict_t *self);
+void SP_trigger_disguise (edict_t *self);
+void SP_monster_stalker (edict_t *self);
+void SP_monster_turret (edict_t *self);
+void SP_target_steam (edict_t *self);
+void SP_target_anger (edict_t *self);
+void SP_target_killplayers (edict_t *self);
+// PMM - still experimental!
+void SP_target_blacklight (edict_t *self);
+void SP_target_orb (edict_t *self);
+// pmm
+//void SP_target_spawn (edict_t *self);
+void SP_hint_path (edict_t *self);
+void SP_monster_carrier (edict_t *self);
+void SP_monster_widow (edict_t *self);
+void SP_monster_widow2 (edict_t *self);
+void SP_dm_tag_token (edict_t *self);
+void SP_dm_dball_goal (edict_t *self);
+void SP_dm_dball_ball (edict_t *self);
+void SP_dm_dball_team1_start (edict_t *self);
+void SP_dm_dball_team2_start (edict_t *self);
+void SP_dm_dball_ball_start (edict_t *self);
+void SP_dm_dball_speed_change (edict_t *self);
+void SP_monster_kamikaze (edict_t *self);
+//void SP_monster_chick2 (edict_t *self);
+void SP_turret_invisible_brain (edict_t *self);
+void SP_xatrix_item (edict_t *self);
+void SP_misc_nuke_core (edict_t *self);
+//ROGUE
+//===========
+
+spawn_t	spawns[] = {
+	{"item_health", SP_item_health},
+	{"item_health_small", SP_item_health_small},
+	{"item_health_large", SP_item_health_large},
+	{"item_health_mega", SP_item_health_mega},
+
+	{"info_player_start", SP_info_player_start},
+	{"info_player_deathmatch", SP_info_player_deathmatch},
+	{"info_player_coop", SP_info_player_coop},
+	{"info_player_intermission", SP_info_player_intermission},
+
+	{"func_plat", SP_func_plat},
+	{"func_button", SP_func_button},
+	{"func_door", SP_func_door},
+	{"func_door_secret", SP_func_door_secret},
+	{"func_door_rotating", SP_func_door_rotating},
+	{"func_rotating", SP_func_rotating},
+	{"func_train", SP_func_train},
+	{"func_water", SP_func_water},
+	{"func_conveyor", SP_func_conveyor},
+	{"func_areaportal", SP_func_areaportal},
+	{"func_clock", SP_func_clock},
+	{"func_wall", SP_func_wall},
+	{"func_object", SP_func_object},
+	{"func_timer", SP_func_timer},
+	{"func_explosive", SP_func_explosive},
+	{"func_killbox", SP_func_killbox},
+
+	{"trigger_always", SP_trigger_always},
+	{"trigger_once", SP_trigger_once},
+	{"trigger_multiple", SP_trigger_multiple},
+	{"trigger_relay", SP_trigger_relay},
+	{"trigger_push", SP_trigger_push},
+	{"trigger_hurt", SP_trigger_hurt},
+	{"trigger_key", SP_trigger_key},
+	{"trigger_counter", SP_trigger_counter},
+	{"trigger_elevator", SP_trigger_elevator},
+	{"trigger_gravity", SP_trigger_gravity},
+	{"trigger_monsterjump", SP_trigger_monsterjump},
+
+	{"target_temp_entity", SP_target_temp_entity},
+	{"target_speaker", SP_target_speaker},
+	{"target_explosion", SP_target_explosion},
+	{"target_changelevel", SP_target_changelevel},
+	{"target_secret", SP_target_secret},
+	{"target_goal", SP_target_goal},
+	{"target_splash", SP_target_splash},
+	{"target_spawner", SP_target_spawner},
+	{"target_blaster", SP_target_blaster},
+	{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
+	{"target_crosslevel_target", SP_target_crosslevel_target},
+	{"target_laser", SP_target_laser},
+	{"target_help", SP_target_help},
+	{"target_actor", SP_target_actor},
+	{"target_lightramp", SP_target_lightramp},
+	{"target_earthquake", SP_target_earthquake},
+	{"target_character", SP_target_character},
+	{"target_string", SP_target_string},
+
+	{"worldspawn", SP_worldspawn},
+	{"viewthing", SP_viewthing},
+
+	{"light", SP_light},
+	{"light_mine1", SP_light_mine1},
+	{"light_mine2", SP_light_mine2},
+	{"info_null", SP_info_null},
+	{"func_group", SP_info_null},
+	{"info_notnull", SP_info_notnull},
+	{"path_corner", SP_path_corner},
+	{"point_combat", SP_point_combat},
+
+	{"misc_explobox", SP_misc_explobox},
+	{"misc_banner", SP_misc_banner},
+	{"misc_satellite_dish", SP_misc_satellite_dish},
+	{"misc_actor", SP_misc_actor},
+	{"misc_gib_arm", SP_misc_gib_arm},
+	{"misc_gib_leg", SP_misc_gib_leg},
+	{"misc_gib_head", SP_misc_gib_head},
+	{"misc_insane", SP_misc_insane},
+	{"misc_deadsoldier", SP_misc_deadsoldier},
+	{"misc_viper", SP_misc_viper},
+	{"misc_viper_bomb", SP_misc_viper_bomb},
+	{"misc_bigviper", SP_misc_bigviper},
+	{"misc_strogg_ship", SP_misc_strogg_ship},
+	{"misc_teleporter", SP_misc_teleporter},
+	{"misc_teleporter_dest", SP_misc_teleporter_dest},
+	{"misc_blackhole", SP_misc_blackhole},
+	{"misc_eastertank", SP_misc_eastertank},
+	{"misc_easterchick", SP_misc_easterchick},
+	{"misc_easterchick2", SP_misc_easterchick2},
+
+	{"monster_berserk", SP_monster_berserk},
+	{"monster_gladiator", SP_monster_gladiator},
+	{"monster_gunner", SP_monster_gunner},
+	{"monster_infantry", SP_monster_infantry},
+	{"monster_soldier_light", SP_monster_soldier_light},
+	{"monster_soldier", SP_monster_soldier},
+	{"monster_soldier_ss", SP_monster_soldier_ss},
+	{"monster_tank", SP_monster_tank},
+	{"monster_tank_commander", SP_monster_tank},
+	{"monster_medic", SP_monster_medic},
+	{"monster_flipper", SP_monster_flipper},
+	{"monster_chick", SP_monster_chick},
+	{"monster_parasite", SP_monster_parasite},
+	{"monster_flyer", SP_monster_flyer},
+	{"monster_brain", SP_monster_brain},
+	{"monster_floater", SP_monster_floater},
+	{"monster_hover", SP_monster_hover},
+	{"monster_mutant", SP_monster_mutant},
+	{"monster_supertank", SP_monster_supertank},
+	{"monster_boss2", SP_monster_boss2},
+	{"monster_boss3_stand", SP_monster_boss3_stand},
+	{"monster_jorg", SP_monster_jorg},
+
+	{"monster_commander_body", SP_monster_commander_body},
+
+	{"turret_breach", SP_turret_breach},
+	{"turret_base", SP_turret_base},
+	{"turret_driver", SP_turret_driver},
+
+//==============
+//ROGUE
+	{"func_plat2", SP_func_plat2},
+	{"func_door_secret2", SP_func_door_secret2},
+	{"func_force_wall", SP_func_force_wall},
+	{"trigger_teleport", SP_trigger_teleport},
+	{"trigger_disguise", SP_trigger_disguise},
+	{"info_teleport_destination", SP_info_teleport_destination},
+	{"info_player_coop_lava", SP_info_player_coop_lava},
+	{"monster_stalker", SP_monster_stalker},
+	{"monster_turret", SP_monster_turret},
+	{"target_steam", SP_target_steam},
+	{"target_anger", SP_target_anger},
+//	{"target_spawn", SP_target_spawn},
+	{"target_killplayers", SP_target_killplayers},
+	// PMM - experiment
+	{"target_blacklight", SP_target_blacklight},
+	{"target_orb", SP_target_orb},
+	// pmm
+	{"monster_daedalus", SP_monster_hover},
+	{"hint_path", SP_hint_path},
+	{"monster_carrier", SP_monster_carrier},
+	{"monster_widow", SP_monster_widow},
+	{"monster_widow2", SP_monster_widow2},
+	{"monster_medic_commander", SP_monster_medic},
+	{"dm_tag_token", SP_dm_tag_token},
+	{"dm_dball_goal", SP_dm_dball_goal},
+	{"dm_dball_ball", SP_dm_dball_ball},
+	{"dm_dball_team1_start", SP_dm_dball_team1_start},
+	{"dm_dball_team2_start", SP_dm_dball_team2_start},
+	{"dm_dball_ball_start", SP_dm_dball_ball_start},
+	{"dm_dball_speed_change", SP_dm_dball_speed_change},
+	{"monster_kamikaze", SP_monster_kamikaze},
+//	{"monster_chick2", SP_monster_chick2},
+	{"turret_invisible_brain", SP_turret_invisible_brain},
+	{"misc_nuke_core", SP_misc_nuke_core},
+
+	{"ammo_magslug", SP_xatrix_item},
+	{"ammo_trap", SP_xatrix_item},
+	{"item_quadfire", SP_xatrix_item},
+	{"weapon_boomer", SP_xatrix_item},
+	{"weapon_phalanx", SP_xatrix_item},
+//ROGUE
+//==============
+
+	{NULL, NULL}
+};
+
+/*
+===============
+ED_CallSpawn
+
+Finds the spawn function for the entity and calls it
+===============
+*/
+void ED_CallSpawn (edict_t *ent)
+{
+	spawn_t	*s;
+	gitem_t	*item;
+	int		i;
+
+	if (!ent->classname)
+	{
+		gi.dprintf ("ED_CallSpawn: NULL classname\n");
+		return;
+	}
+
+//PGM - do this before calling the spawn function so it can be overridden.
+#ifdef ROGUE_GRAVITY
+	ent->gravityVector[0] =  0.0;
+	ent->gravityVector[1] =  0.0;
+	ent->gravityVector[2] = -1.0;
+#endif
+//PGM
+
+	// FIXME - PMM classnames hack
+	if (!strcmp(ent->classname, "weapon_nailgun"))
+		ent->classname = (FindItem("ETF Rifle"))->classname;
+	if (!strcmp(ent->classname, "ammo_nails"))
+		ent->classname = (FindItem("Flechettes"))->classname;
+	if (!strcmp(ent->classname, "weapon_heatbeam"))
+		ent->classname = (FindItem("Plasma Beam"))->classname;
+	// pmm
+
+	// check item spawn functions
+	for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
+	{
+		if (!item->classname)
+			continue;
+		if (!strcmp(item->classname, ent->classname))
+		{	// found it
+			SpawnItem (ent, item);
+			return;
+		}
+	}
+
+	// check normal spawn functions
+	for (s=spawns ; s->name ; s++)
+	{
+		if (!strcmp(s->name, ent->classname))
+		{	// found it
+			s->spawn (ent);
+			return;
+		}
+	}
+	gi.dprintf ("%s doesn't have a spawn function\n", ent->classname);
+}
+
+/*
+=============
+ED_NewString
+=============
+*/
+char *ED_NewString (char *string)
+{
+	char	*newb, *new_p;
+	int		i,l;
+	
+	l = strlen(string) + 1;
+
+	newb = gi.TagMalloc (l, TAG_LEVEL);
+
+	new_p = newb;
+
+	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;
+}
+
+
+
+
+/*
+===============
+ED_ParseField
+
+Takes a key/value pair and sets the binary values
+in an edict
+===============
+*/
+void ED_ParseField (char *key, char *value, edict_t *ent)
+{
+	field_t	*f;
+	byte	*b;
+	float	v;
+	vec3_t	vec;
+
+	for (f=fields ; f->name ; f++)
+	{
+		if (!(f->flags & FFL_NOSPAWN) && !cistrcmp(f->name, key))
+		{	// found it
+			if (f->flags & FFL_SPAWNTEMP)
+				b = (byte *)&st;
+			else
+				b = (byte *)ent;
+
+			switch (f->type)
+			{
+			case F_LSTRING:
+				*(char **)(b+f->ofs) = ED_NewString (value);
+				break;
+			case F_VECTOR:
+				sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
+				((float *)(b+f->ofs))[0] = vec[0];
+				((float *)(b+f->ofs))[1] = vec[1];
+				((float *)(b+f->ofs))[2] = vec[2];
+				break;
+			case F_INT:
+				*(int *)(b+f->ofs) = atoi(value);
+				break;
+			case F_FLOAT:
+				*(float *)(b+f->ofs) = atof(value);
+				break;
+			case F_ANGLEHACK:
+				v = atof(value);
+				((float *)(b+f->ofs))[0] = 0;
+				((float *)(b+f->ofs))[1] = v;
+				((float *)(b+f->ofs))[2] = 0;
+				break;
+			case F_IGNORE:
+				break;
+			}
+			return;
+		}
+	}
+	gi.dprintf ("%s is not a field\n", key);
+}
+
+/*
+====================
+ED_ParseEdict
+
+Parses an edict out of the given string, returning the new position
+ed should be a properly initialized empty edict.
+====================
+*/
+char *ED_ParseEdict (char *data, edict_t *ent)
+{
+	qboolean	init;
+	char		keyname[256];
+	char		*com_token;
+
+	init = false;
+	memset (&st, 0, sizeof(st));
+
+// go through all the dictionary pairs
+	while (1)
+	{	
+	// parse key
+		com_token = COM_Parse (&data);
+		if (com_token[0] == '}')
+			break;
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		strncpy (keyname, com_token, sizeof(keyname)-1);
+		
+	// parse value	
+		com_token = COM_Parse (&data);
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		if (com_token[0] == '}')
+			gi.error ("ED_ParseEntity: closing brace without data");
+
+		init = true;	
+
+	// keynames with a leading underscore are used for utility comments,
+	// and are immediately discarded by quake
+		if (keyname[0] == '_')
+			continue;
+
+		ED_ParseField (keyname, com_token, ent);
+	}
+
+	if (!init)
+		memset (ent, 0, sizeof(*ent));
+
+	return data;
+}
+
+
+/*
+================
+G_FindTeams
+
+Chain together all entities with a matching team field.
+
+All but the first will have the FL_TEAMSLAVE flag set.
+All but the last will have the teamchain field set to the next one
+================
+*/
+void G_FixTeams (void)
+{
+	edict_t	*e, *e2, *chain;
+	int		i, j;
+	int		c, c2;
+
+	c = 0;
+	c2 = 0;
+	for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->team)
+			continue;
+		if (!strcmp(e->classname, "func_train"))
+		{
+			if(e->flags & FL_TEAMSLAVE)
+			{
+				chain = e;
+				e->teammaster = e;
+				e->teamchain = NULL;
+				e->flags &= ~FL_TEAMSLAVE;
+				c++;
+				c2++;
+				for (j=1, e2=g_edicts+j ; j < globals.num_edicts ; j++,e2++)
+				{
+					if (e2 == e)
+						continue;
+					if (!e2->inuse)
+						continue;
+					if (!e2->team)
+						continue;
+					if (!strcmp(e->team, e2->team))
+					{
+						c2++;
+						chain->teamchain = e2;
+						e2->teammaster = e;
+						e2->teamchain = NULL;
+						chain = e2;
+						e2->flags |= FL_TEAMSLAVE;
+						e2->movetype = MOVETYPE_PUSH;
+						e2->speed = e->speed;
+					}
+				}
+			}
+		}
+	}
+	gi.dprintf ("%i teams repaired\n", c);
+}
+
+void G_FindTeams (void)
+{
+	edict_t	*e, *e2, *chain;
+	int		i, j;
+	int		c, c2;
+
+	c = 0;
+	c2 = 0;
+	for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->team)
+			continue;
+		if (e->flags & FL_TEAMSLAVE)
+			continue;
+		chain = e;
+		e->teammaster = e;
+		c++;
+		c2++;
+		for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++)
+		{
+			if (!e2->inuse)
+				continue;
+			if (!e2->team)
+				continue;
+			if (e2->flags & FL_TEAMSLAVE)
+				continue;
+			if (!strcmp(e->team, e2->team))
+			{
+				c2++;
+				chain->teamchain = e2;
+				e2->teammaster = e;
+				chain = e2;
+				e2->flags |= FL_TEAMSLAVE;
+			}
+		}
+	}
+
+	G_FixTeams();
+
+	gi.dprintf ("%i teams with %i entities\n", c, c2);
+}
+
+/*
+==============
+SpawnEntities
+
+Creates a server's entity / program execution context by
+parsing textual entity definitions out of an ent file.
+==============
+*/
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint)
+{
+	edict_t		*ent;
+	int			inhibit;
+	char		*com_token;
+	int			i;
+	float		skill_level;
+
+	skill_level = floor (skill->value);
+	if (skill_level < 0)
+		skill_level = 0;
+	if (skill_level > 3)
+		skill_level = 3;
+	if (skill->value != skill_level)
+		gi.cvar_forceset("skill", va("%f", skill_level));
+
+	SaveClientData ();
+
+	gi.FreeTags (TAG_LEVEL);
+
+	memset (&level, 0, sizeof(level));
+	memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
+
+	strncpy (level.mapname, mapname, sizeof(level.mapname)-1);
+	strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1);
+
+	// set client fields on player ents
+	for (i=0 ; i<game.maxclients ; i++)
+		g_edicts[i+1].client = game.clients + i;
+
+	ent = NULL;
+	inhibit = 0;
+
+// parse ents
+	while (1)
+	{
+		// parse the opening brace	
+		com_token = COM_Parse (&entities);
+		if (!entities)
+			break;
+		if (com_token[0] != '{')
+			gi.error ("ED_LoadFromFile: found %s when expecting {",com_token);
+
+		if (!ent)
+			ent = g_edicts;
+		else
+			ent = G_Spawn ();
+		entities = ED_ParseEdict (entities, ent);
+
+		// yet another map hack
+		if (!cistrcmp(level.mapname, "command") && !cistrcmp(ent->classname, "trigger_once") && !cistrcmp(ent->model, "*27"))
+			ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
+		
+		// ROGUE
+		//ahh, the joys of map hacks .. 
+		if (!cistrcmp(level.mapname, "rhangar2") && !cistrcmp(ent->classname, "func_door_rotating") && ent->targetname && !cistrcmp(ent->targetname, "t265"))
+			ent->spawnflags &= ~SPAWNFLAG_NOT_COOP;
+		if (!cistrcmp(level.mapname, "rhangar2") && !cistrcmp(ent->classname, "trigger_always") && ent->target && !cistrcmp(ent->target, "t265"))
+			ent->spawnflags |= SPAWNFLAG_NOT_COOP;
+		if (!cistrcmp(level.mapname, "rhangar2") && !cistrcmp(ent->classname, "func_wall") && !cistrcmp(ent->model, "*15"))
+			ent->spawnflags |= SPAWNFLAG_NOT_COOP;
+		// rogue
+
+		// remove things (except the world) from different skill levels or deathmatch
+		if (ent != g_edicts)
+		{
+			if (deathmatch->value)
+			{
+				if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
+				{
+					G_FreeEdict (ent);	
+					inhibit++;
+					continue;
+				}
+			}
+			else if(coop->value)
+			{
+				if (ent->spawnflags & SPAWNFLAG_NOT_COOP)
+				{
+					G_FreeEdict (ent);	
+					inhibit++;
+					continue;
+				}
+
+				// stuff marked !easy & !med & !hard are coop only, all levels
+				if(!((ent->spawnflags & SPAWNFLAG_NOT_EASY) &&
+					(ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) &&
+					(ent->spawnflags & SPAWNFLAG_NOT_HARD)) )
+				{
+					if( ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+						((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+						(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
+					  )
+					{
+						G_FreeEdict (ent);	
+						inhibit++;
+						continue;
+					}
+				}
+			}
+			else
+			{
+				if ( /*((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ 
+					((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+					((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+					(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
+					)
+					{
+						G_FreeEdict (ent);	
+						inhibit++;
+						continue;
+					}
+			}
+
+			ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH);
+		}
+
+//PGM - do this before calling the spawn function so it can be overridden.
+#ifdef ROGUE_GRAVITY
+		ent->gravityVector[0] =  0.0;
+		ent->gravityVector[1] =  0.0;
+		ent->gravityVector[2] = -1.0;
+#endif
+//PGM
+		ED_CallSpawn (ent);
+
+		ent->s.renderfx |= RF_IR_VISIBLE;		//PGM
+	}	
+
+	gi.dprintf ("%i entities inhibited\n", inhibit);
+
+#ifdef DEBUG
+	i = 1;
+	ent = EDICT_NUM(i);
+	while (i < globals.num_edicts) {
+		if (ent->inuse != 0 || ent->inuse != 1)
+			Com_DPrintf("Invalid entity %d\n", i);
+		i++, ent++;
+	}
+#endif
+
+	G_FindTeams ();
+
+	PlayerTrail_Init ();
+
+//ROGUE
+	if(deathmatch->value)
+	{
+		if(randomrespawn && randomrespawn->value)
+			PrecacheForRandomRespawn();
+	}
+	else
+	{
+		InitHintPaths();		// if there aren't hintpaths on this map, enable quick aborts
+	}
+//ROGUE
+
+// ROGUE	-- allow dm games to do init stuff right before game starts.
+	if(deathmatch->value && gamerules && gamerules->value)
+	{
+		if(DMGame.PostInitSetup)
+			DMGame.PostInitSetup ();
+	}
+// ROGUE
+}
+
+
+//===================================================================
+
+/*
+	// cursor positioning
+	xl <value>
+	xr <value>
+	yb <value>
+	yt <value>
+	xv <value>
+	yv <value>
+
+	// drawing
+	statpic <name>
+	pic <stat>
+	num <fieldwidth> <stat>
+	string <stat>
+
+	// control
+	if <stat>
+	ifeq <stat> <value>
+	ifbit <stat> <value>
+	endif
+*/
+
+char *single_statusbar = 
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	262 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+;
+
+char *dm_statusbar =
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	246 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+
+//  frags
+"xr	-50 "
+"yt 2 "
+"num 3 14 "
+
+// spectator
+"if 17 "
+  "xv 0 "
+  "yb -58 "
+  "string2 \"SPECTATOR MODE\" "
+"endif "
+
+// chase camera
+"if 16 "
+  "xv 0 "
+  "yb -68 "
+  "string \"Chasing\" "
+  "xv 64 "
+  "stat_string 16 "
+"endif "
+;
+
+
+/*QUAKED worldspawn (0 0 0) ?
+
+Only used for the world.
+"sky"	environment map name
+"skyaxis"	vector axis for rotating sky
+"skyrotate"	speed of rotation in degrees/second
+"sounds"	music cd track number
+"gravity"	800 is default gravity
+"message"	text to print at user logon
+*/
+void SP_worldspawn (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	ent->inuse = true;			// since the world doesn't use G_Spawn()
+	ent->s.modelindex = 1;		// world model is always index 1
+
+	//---------------
+
+	// reserve some spots for dead player bodies for coop / deathmatch
+	InitBodyQue ();
+
+	// set configstrings for items
+	SetItemNames ();
+
+	if (st.nextmap)
+		strcpy (level.nextmap, st.nextmap);
+
+	// make some data visible to the server
+
+	if (ent->message && ent->message[0])
+	{
+		gi.configstring (CS_NAME, ent->message);
+		strncpy (level.level_name, ent->message, sizeof(level.level_name));
+	}
+	else
+		strncpy (level.level_name, level.mapname, sizeof(level.level_name));
+
+	if (st.sky && st.sky[0])
+		gi.configstring (CS_SKY, st.sky);
+	else
+		gi.configstring (CS_SKY, "unit1_");
+
+	gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) );
+
+	gi.configstring (CS_SKYAXIS, va("%f %f %f",
+		st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) );
+
+	gi.configstring (CS_CDTRACK, va("%i", ent->sounds) );
+
+	gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) );
+
+	// status bar program
+	if (deathmatch->value)
+		gi.configstring (CS_STATUSBAR, dm_statusbar);
+	else
+		gi.configstring (CS_STATUSBAR, single_statusbar);
+
+	//---------------
+
+
+	// help icon for statusbar
+	gi.imageindex ("i_help");
+	level.pic_health = gi.imageindex ("i_health");
+	gi.imageindex ("help");
+	gi.imageindex ("field_3");
+
+	if (!st.gravity)
+		gi.cvar_set("sv_gravity", "800");
+	else
+		gi.cvar_set("sv_gravity", st.gravity);
+
+	snd_fry = gi.soundindex ("player/fry.wav");	// standing in lava / slime
+
+	PrecacheItem (FindItem ("Blaster"));
+
+	gi.soundindex ("player/lava1.wav");
+	gi.soundindex ("player/lava2.wav");
+
+	gi.soundindex ("misc/pc_up.wav");
+	gi.soundindex ("misc/talk1.wav");
+
+	gi.soundindex ("misc/udeath.wav");
+
+	// gibs
+	gi.soundindex ("items/respawn1.wav");
+
+	// sexed sounds
+	gi.soundindex ("*death1.wav");
+	gi.soundindex ("*death2.wav");
+	gi.soundindex ("*death3.wav");
+	gi.soundindex ("*death4.wav");
+	gi.soundindex ("*fall1.wav");
+	gi.soundindex ("*fall2.wav");	
+	gi.soundindex ("*gurp1.wav");		// drowning damage
+	gi.soundindex ("*gurp2.wav");	
+	gi.soundindex ("*jump1.wav");		// player jump
+	gi.soundindex ("*pain25_1.wav");
+	gi.soundindex ("*pain25_2.wav");
+	gi.soundindex ("*pain50_1.wav");
+	gi.soundindex ("*pain50_2.wav");
+	gi.soundindex ("*pain75_1.wav");
+	gi.soundindex ("*pain75_2.wav");
+	gi.soundindex ("*pain100_1.wav");
+	gi.soundindex ("*pain100_2.wav");
+
+	// sexed models
+	// THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+	// you can add more, max 19 (pete change)
+	// these models are only loaded in coop or deathmatch. not singleplayer.
+	if (coop->value || deathmatch->value)
+	{
+		gi.modelindex ("#w_blaster.md2");
+		gi.modelindex ("#w_shotgun.md2");
+		gi.modelindex ("#w_sshotgun.md2");
+		gi.modelindex ("#w_machinegun.md2");
+		gi.modelindex ("#w_chaingun.md2");
+		gi.modelindex ("#a_grenades.md2");
+		gi.modelindex ("#w_glauncher.md2");
+		gi.modelindex ("#w_rlauncher.md2");
+		gi.modelindex ("#w_hyperblaster.md2");
+		gi.modelindex ("#w_railgun.md2");
+		gi.modelindex ("#w_bfg.md2");
+
+		gi.modelindex ("#w_disrupt.md2");			// PGM
+		gi.modelindex ("#w_etfrifle.md2");			// PGM
+		gi.modelindex ("#w_plasma.md2");			// PGM
+		gi.modelindex ("#w_plauncher.md2");			// PGM
+		gi.modelindex ("#w_chainfist.md2");			// PGM
+	}
+
+	//-------------------
+
+	gi.soundindex ("player/gasp1.wav");		// gasping for air
+	gi.soundindex ("player/gasp2.wav");		// head breaking surface, not gasping
+
+	gi.soundindex ("player/watr_in.wav");	// feet hitting water
+	gi.soundindex ("player/watr_out.wav");	// feet leaving water
+
+	gi.soundindex ("player/watr_un.wav");	// head going underwater
+	
+	gi.soundindex ("player/u_breath1.wav");
+	gi.soundindex ("player/u_breath2.wav");
+
+	gi.soundindex ("items/pkup.wav");		// bonus item pickup
+	gi.soundindex ("world/land.wav");		// landing thud
+	gi.soundindex ("misc/h2ohit1.wav");		// landing splash
+
+	gi.soundindex ("items/damage.wav");
+	// ROGUE - double damage
+	gi.soundindex ("misc/ddamage1.wav");
+	// rogue
+	gi.soundindex ("items/protect.wav");
+	gi.soundindex ("items/protect4.wav");
+	gi.soundindex ("weapons/noammo.wav");
+
+	gi.soundindex ("infantry/inflies1.wav");
+
+	sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
+	gi.modelindex ("models/objects/gibs/arm/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone2/tris.md2");
+	gi.modelindex ("models/objects/gibs/chest/tris.md2");
+	gi.modelindex ("models/objects/gibs/skull/tris.md2");
+	gi.modelindex ("models/objects/gibs/head2/tris.md2");
+
+//
+// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
+//
+
+	// 0 normal
+	gi.configstring(CS_LIGHTS+0, "m");
+	
+	// 1 FLICKER (first variety)
+	gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo");
+	
+	// 2 SLOW STRONG PULSE
+	gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+	
+	// 3 CANDLE (first variety)
+	gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+	
+	// 4 FAST STROBE
+	gi.configstring(CS_LIGHTS+4, "mamamamamama");
+	
+	// 5 GENTLE PULSE 1
+	gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+	
+	// 6 FLICKER (second variety)
+	gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno");
+	
+	// 7 CANDLE (second variety)
+	gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm");
+	
+	// 8 CANDLE (third variety)
+	gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+	
+	// 9 SLOW STROBE (fourth variety)
+	gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz");
+	
+	// 10 FLUORESCENT FLICKER
+	gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma");
+
+	// 11 SLOW PULSE NOT FADE TO BLACK
+	gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
+	
+	// styles 32-62 are assigned by the light program for switchable lights
+
+	// 63 testing
+	gi.configstring(CS_LIGHTS+63, "a");
+}
+
+//
+//ROGUE
+//
+
+//
+// Monster spawning code
+//
+// Used by the carrier, the medic_commander, and the black widow
+//
+// The sequence to create a flying monster is:
+//
+//  FindSpawnPoint - tries to find suitable spot to spawn the monster in
+//  CreateFlyMonster  - this verifies the point as good and creates the monster
+
+// To create a ground walking monster:
+//
+//  FindSpawnPoint - same thing
+//  CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable
+//
+
+// FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things
+
+//
+// CreateMonster
+//
+edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname)
+{
+	edict_t		*newEnt;
+
+	newEnt = G_Spawn();
+
+	VectorCopy(origin, newEnt->s.origin);
+	VectorCopy(angles, newEnt->s.angles);
+	newEnt->classname = ED_NewString (classname);
+	newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+	
+	VectorSet(newEnt->gravityVector, 0, 0, -1);
+	ED_CallSpawn(newEnt);
+	newEnt->s.renderfx |= RF_IR_VISIBLE;
+
+	return newEnt;
+}
+
+edict_t *CreateFlyMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname)
+{
+	if (!mins || !maxs || VectorCompare (mins, vec3_origin) || VectorCompare (maxs, vec3_origin))
+	{
+		DetermineBBox (classname, mins, maxs);
+	}
+
+	if (!CheckSpawnPoint(origin, mins, maxs))
+		return NULL;
+
+	return (CreateMonster (origin, angles, classname));
+}
+
+// This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there
+// are bad things down there or not
+//
+// this is from m_move.c
+#define	STEPSIZE	18
+
+edict_t *CreateGroundMonster (vec3_t origin, vec3_t angles, vec3_t entMins, vec3_t entMaxs, char *classname, int height)
+{
+//	trace_t		tr;
+	edict_t		*newEnt;
+//	vec3_t		start, stop;
+//	int			failure = 0;
+//	vec3_t		mins, maxs;
+//	int			x, y;	
+//	float		mid, bottom;
+	vec3_t		mins, maxs;
+
+	// if they don't provide us a bounding box, figure it out
+	if (!entMins || !entMaxs || VectorCompare (entMins, vec3_origin) || VectorCompare (entMaxs, vec3_origin))
+	{
+		DetermineBBox (classname, mins, maxs);
+	}
+	else
+	{
+		VectorCopy (entMins, mins);
+		VectorCopy (entMaxs, maxs);
+	}
+
+	// check the ground to make sure it's there, it's relatively flat, and it's not toxic
+	if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1))
+		return NULL;
+
+	newEnt = CreateMonster (origin, angles, classname);
+	if (!newEnt)
+		return NULL;
+
+	return newEnt;
+}
+
+
+// FindSpawnPoint
+// PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point
+// if the startpoint is bad, try above the startpoint for a bit
+
+qboolean FindSpawnPoint (vec3_t startpoint, vec3_t mins, vec3_t maxs, vec3_t spawnpoint, float maxMoveUp)
+{
+	trace_t		tr;
+	vec3_t		top;
+
+	tr = gi.trace (startpoint, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID|CONTENTS_PLAYERCLIP);
+	if((tr.startsolid || tr.allsolid) || (tr.ent != WORLD))
+	{
+//		if ( ((tr.ent->svflags & SVF_MONSTER) && (tr.ent->health <= 0)) ||
+//			 (tr.ent->svflags & SVF_DAMAGEABLE) )
+//		{
+//			T_Damage (tr.ent, self, self, vec3_origin, self->enemy->s.origin,
+//						pain_normal, hurt, 0, 0, MOD_UNKNOWN);
+
+		VectorCopy (startpoint, top);
+		top[2] += maxMoveUp;
+/*
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_DEBUGTRAIL);
+		gi.WritePosition (top);
+		gi.WritePosition (startpoint);
+		gi.multicast (startpoint, MULTICAST_ALL);	
+*/
+		tr = gi.trace (top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID);
+		if (tr.startsolid || tr.allsolid)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				if (tr.ent)
+//					gi.dprintf("FindSpawnPoint: failed to find a point -- blocked by %s\n", tr.ent->classname);
+//				else
+//					gi.dprintf("FindSpawnPoint: failed to find a point\n");
+
+			return false;
+		} 
+		else
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("FindSpawnPoint: %s -> %s\n", vtos (startpoint), vtos (tr.endpos));
+			VectorCopy (tr.endpos, spawnpoint);
+			return true;
+		}
+	}
+	else
+	{
+		VectorCopy (startpoint, spawnpoint);
+		return true;
+	}
+}
+
+// FIXME - all of this needs to be tweaked to handle the new gravity rules
+// if we ever want to spawn stuff on the roof
+
+//
+// CheckSpawnPoint
+//
+// PMM - checks volume to make sure we can spawn a monster there (is it solid?)
+//
+// This is all fliers should need
+
+qboolean CheckSpawnPoint (vec3_t origin, vec3_t mins, vec3_t maxs)
+{
+	trace_t	tr;
+
+	if (!mins || !maxs || VectorCompare(mins, vec3_origin) || VectorCompare (maxs, vec3_origin))
+	{
+		return false;
+	}
+
+	tr = gi.trace (origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID);
+	if(tr.startsolid || tr.allsolid)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf("createmonster in wall. removing\n");
+		return false;
+	}
+	if (tr.ent != WORLD)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf("createmonster in entity %s\n", tr.ent->classname);
+		return false;
+	}	
+	return true;
+}
+
+//
+// CheckGroundSpawnPoint
+//
+// PMM - used for walking monsters
+//  checks:
+//		1)	is there a ground within the specified height of the origin?
+//		2)	is the ground non-water?
+//		3)	is the ground flat enough to walk on?
+//
+
+qboolean CheckGroundSpawnPoint (vec3_t origin, vec3_t entMins, vec3_t entMaxs, float height, float gravity)
+{
+	trace_t		tr;
+	vec3_t		start, stop;
+	vec3_t		mins, maxs;
+	int			x, y;	
+	float		mid, bottom;
+
+	if (!CheckSpawnPoint (origin, entMins, entMaxs))
+		return false;
+
+	// FIXME - this is too conservative about angled surfaces
+
+	VectorCopy (origin, stop);
+	// FIXME - gravity vector
+	stop[2] = origin[2] + entMins[2] - height;
+
+	/*
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_DEBUGTRAIL);
+	gi.WritePosition (origin);
+	gi.WritePosition (stop);
+	gi.multicast (start, MULTICAST_ALL);
+	*/
+
+	tr = gi.trace (origin, entMins, entMaxs, stop, NULL, MASK_MONSTERSOLID | MASK_WATER);
+	// it's not going to be all solid or start solid, since that's checked above
+
+	if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID))
+	{
+		// we found a non-water surface down there somewhere.  now we need to check to make sure it's not too sloped
+		//
+		// algorithm straight out of m_move.c:M_CheckBottom()
+		//
+
+		// first, do the midpoint trace
+
+		VectorAdd (tr.endpos, entMins, mins);
+		VectorAdd (tr.endpos, entMaxs, maxs);
+
+
+		// first, do the easy flat check
+		//
+#ifdef ROGUE_GRAVITY
+		// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+		if(gravity > 0)
+			start[2] = maxs[2] + 1;
+		else
+			start[2] = mins[2] - 1;
+#else
+		start[2] = mins[2] - 1;
+#endif
+		for	(x=0 ; x<=1 ; x++)
+		{
+			for	(y=0 ; y<=1 ; y++)
+			{
+				start[0] = x ? maxs[0] : mins[0];
+				start[1] = y ? maxs[1] : mins[1];
+				if (gi.pointcontents (start) != CONTENTS_SOLID)
+					goto realcheck;
+			}
+		}
+
+		// if it passed all four above checks, we're done
+		return true;
+
+realcheck:
+
+		// check it for real
+
+		start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+		start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+		start[2] = mins[2];
+
+		tr = gi.trace (start, vec3_origin, vec3_origin, stop, NULL, MASK_MONSTERSOLID);
+
+		if (tr.fraction == 1.0)
+			return false;
+
+#ifdef ROGUE_GRAVITY
+		if(gravity < 0)
+		{
+			start[2] = mins[2];
+			stop[2] = start[2] - STEPSIZE - STEPSIZE;
+			mid = bottom = tr.endpos[2] + entMins[2];
+		}
+		else
+		{
+			start[2] = maxs[2];
+			stop[2] = start[2] + STEPSIZE + STEPSIZE;
+			mid = bottom = tr.endpos[2] - entMaxs[2];
+		}
+#else
+		stop[2] = start[2] - 2*STEPSIZE;
+		mid = bottom = tr.endpos[2] + entMins[2];
+#endif
+
+		for	(x=0 ; x<=1 ; x++)
+			for	(y=0 ; y<=1 ; y++)
+			{
+				start[0] = stop[0] = x ? maxs[0] : mins[0];
+				start[1] = stop[1] = y ? maxs[1] : mins[1];
+				
+				/*
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_DEBUGTRAIL);
+				gi.WritePosition (start);
+				gi.WritePosition (stop);
+				gi.multicast (start, MULTICAST_ALL);	
+				*/
+				tr = gi.trace (start, vec3_origin, vec3_origin, stop, NULL, MASK_MONSTERSOLID);
+
+//PGM
+#ifdef ROGUE_GRAVITY
+// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+				if(gravity > 0)
+				{
+					if (tr.fraction != 1.0 && tr.endpos[2] < bottom)
+						bottom = tr.endpos[2];
+					if (tr.fraction == 1.0 || tr.endpos[2] - mid > STEPSIZE)
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("spawn - rejecting due to uneven ground\n");
+						return false;
+					}
+				}
+				else
+				{
+					if (tr.fraction != 1.0 && tr.endpos[2] > bottom)
+						bottom = tr.endpos[2];
+					if (tr.fraction == 1.0 || mid - tr.endpos[2] > STEPSIZE)
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("spawn - rejecting due to uneven ground\n");
+						return false;
+					}
+				}
+#else
+				if (tr.fraction != 1.0 && tr.endpos[2] > bottom)
+					bottom = tr.endpos[2];
+				if (tr.fraction == 1.0 || mid - tr.endpos[2] > STEPSIZE)
+					{
+						return false;
+					}
+#endif
+			}
+
+		return true;		// we can land on it, it's ok
+	}
+
+	// otherwise, it's either water (bad) or not there (too far)
+	// if we're here, it's bad below
+//	if ((g_showlogic) && (g_showlogic->value))
+//	{
+//		if (tr.fraction < 1)
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf("groundmonster would fall into water/slime/lava\n");
+//		else
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf("groundmonster would fall too far\n");
+//	}
+
+	return false;
+}
+
+void DetermineBBox (char *classname, vec3_t mins, vec3_t maxs)
+{
+	// FIXME - cache this stuff
+	edict_t		*newEnt;
+
+	newEnt = G_Spawn();
+
+	VectorCopy(vec3_origin, newEnt->s.origin);
+	VectorCopy(vec3_origin, newEnt->s.angles);
+	newEnt->classname = ED_NewString (classname);
+	newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+	
+	ED_CallSpawn(newEnt);
+	
+	VectorCopy (newEnt->mins, mins);
+	VectorCopy (newEnt->maxs, maxs);
+
+	G_FreeEdict (newEnt);
+}
+
+// ****************************
+// SPAWNGROW stuff
+// ****************************
+
+#define SPAWNGROW_LIFESPAN		0.3
+
+void spawngrow_think (edict_t *self)
+{
+	int i;
+
+	for (i=0; i<2; i++)
+	{
+			self->s.angles[0] = rand()%360;
+			self->s.angles[1] = rand()%360;
+			self->s.angles[2] = rand()%360;
+	}
+	if ((level.time < self->wait) && (self->s.frame < 2))
+		self->s.frame++;
+	if (level.time >= self->wait)
+	{
+		if (self->s.effects & EF_SPHERETRANS)
+		{
+			G_FreeEdict (self);
+			return;
+		}
+		else if (self->s.frame > 0)
+			self->s.frame--;
+		else
+		{
+			G_FreeEdict (self);
+			return;
+		}
+	}
+	self->nextthink += FRAMETIME;
+}
+
+void SpawnGrow_Spawn (vec3_t startpos, int size)
+{
+	edict_t *ent;
+	int	i;
+	float	lifespan;
+
+	ent = G_Spawn();
+	VectorCopy(startpos, ent->s.origin);
+	for (i=0; i<2; i++)
+	{
+			ent->s.angles[0] = rand()%360;
+			ent->s.angles[1] = rand()%360;
+			ent->s.angles[2] = rand()%360;
+	}
+	ent->solid = SOLID_NOT;
+//	ent->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
+	ent->s.renderfx = RF_IR_VISIBLE;
+	ent->movetype = MOVETYPE_NONE;
+	ent->classname = "spawngro";
+
+	if (size <= 1)
+	{
+		lifespan = SPAWNGROW_LIFESPAN;
+		ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2");
+	}
+	else if (size == 2)
+	{
+		ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
+		lifespan = 2;
+	}
+	else
+	{
+		ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2");
+		lifespan = SPAWNGROW_LIFESPAN;
+	}
+
+	ent->think = spawngrow_think;
+
+	ent->wait = level.time + lifespan;
+	ent->nextthink = level.time + FRAMETIME;
+	if (size != 2)
+		ent->s.effects |= EF_SPHERETRANS;
+	gi.linkentity (ent);
+}
+
+
+// ****************************
+// WidowLeg stuff
+// ****************************
+
+#define	MAX_LEGSFRAME	23
+#define	LEG_WAIT_TIME	1
+
+void ThrowMoreStuff (edict_t *self, vec3_t point);
+void ThrowSmallStuff (edict_t *self, vec3_t point);
+void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade);
+void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade);
+
+void widowlegs_think (edict_t *self)
+{
+	vec3_t	offset;
+	vec3_t	point;
+	vec3_t	f,r,u;
+
+	if (self->s.frame == 17)
+	{
+		VectorSet (offset, 11.77, -7.24, 23.31);
+		AngleVectors (self->s.angles, f, r, u);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1);
+		gi.WritePosition (point);
+		gi.multicast (point, MULTICAST_ALL);
+		ThrowSmallStuff (self, point);
+	}
+
+	if (self->s.frame < MAX_LEGSFRAME)
+	{
+		self->s.frame++;
+		self->nextthink = level.time + FRAMETIME;
+		return;
+	}
+	else if (self->wait == 0)
+	{
+		self->wait = level.time + LEG_WAIT_TIME;
+	}
+	if (level.time > self->wait)
+	{
+		AngleVectors (self->s.angles, f, r, u);
+
+		VectorSet (offset, -65.6, -8.44, 28.59);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1);
+		gi.WritePosition (point);
+		gi.multicast (point, MULTICAST_ALL);
+		ThrowSmallStuff (self, point);
+
+		ThrowWidowGibSized (self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int)(qrandom()*20.0), GIB_METALLIC, point, 0, true);
+		ThrowWidowGibSized (self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int)(qrandom()*20.0), GIB_METALLIC, point, 0, true);
+
+		VectorSet (offset, -1.04, -51.18, 7.04);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1);
+		gi.WritePosition (point);
+		gi.multicast (point, MULTICAST_ALL);
+		ThrowSmallStuff (self, point);
+
+		ThrowWidowGibSized (self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int)(qrandom()*20.0), GIB_METALLIC, point, 0, true);
+		ThrowWidowGibSized (self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int)(qrandom()*20.0), GIB_METALLIC, point, 0, true);
+		ThrowWidowGibSized (self, "models/monsters/blackwidow/gib3/tris.md2", 80 + (int)(qrandom()*20.0), GIB_METALLIC, point, 0, true);
+
+		G_FreeEdict (self);
+		return;
+	}
+	if ((level.time > (self->wait - 0.5)) && (self->count == 0))
+	{
+		self->count = 1;
+		AngleVectors (self->s.angles, f, r, u);
+
+		VectorSet (offset, 31, -88.7, 10.96);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1);
+		gi.WritePosition (point);
+		gi.multicast (point, MULTICAST_ALL);
+//		ThrowSmallStuff (self, point);
+
+		VectorSet (offset, -12.67, -4.39, 15.68);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, point);
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1);
+		gi.WritePosition (point);
+		gi.multicast (point, MULTICAST_ALL);
+//		ThrowSmallStuff (self, point);
+
+		self->nextthink = level.time + FRAMETIME;
+		return;
+	}
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void Widowlegs_Spawn (vec3_t startpos, vec3_t angles)
+{
+	edict_t *ent;
+
+	ent = G_Spawn();
+	VectorCopy(startpos, ent->s.origin);
+	VectorCopy(angles, ent->s.angles);
+	ent->solid = SOLID_NOT;
+	ent->s.renderfx = RF_IR_VISIBLE;
+	ent->movetype = MOVETYPE_NONE;
+	ent->classname = "widowlegs";
+
+	ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2");
+	ent->think = widowlegs_think;
+
+	ent->nextthink = level.time + FRAMETIME;
+	gi.linkentity (ent);
+}
--- /dev/null
+++ b/rogue/g_sphere.c
@@ -1,0 +1,787 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+// defender - actively finds and shoots at enemies
+// hunter - waits until < 25% health and vore ball tracks person who hurt you
+// vengeance - kills person who killed you.
+
+#define DEFENDER_LIFESPAN	30
+#define HUNTER_LIFESPAN		30
+#define VENGEANCE_LIFESPAN	30
+#define MINIMUM_FLY_TIME	15
+//#define MINIMUM_FLY_TIME	30
+
+// FIXME - do we need to be calling ED_NewString at all?
+extern char *ED_NewString (char *string);
+void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker);
+
+
+void defender_think (edict_t *self);
+void hunter_think (edict_t *self);
+void vengeance_think (edict_t *self);
+void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+// *************************
+// General Sphere Code
+// *************************
+
+// =================
+// =================
+void sphere_think_explode (edict_t *self)
+{
+	if(self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER))
+	{
+		self->owner->client->owned_sphere = NULL;
+	}
+	BecomeExplosion1 (self);
+}
+
+// =================
+// sphere_explode
+// =================
+void sphere_explode (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+//	if(self->owner && self->owner->client)
+//		gi.cprintf(self->owner, PRINT_HIGH, "Sphere timed out\n");
+//	gi.dprintf("player died, blowing up\n");
+	sphere_think_explode (self);
+}
+
+// =================
+// sphere_if_idle_die - if the sphere is not currently attacking, blow up.
+// =================
+void sphere_if_idle_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	if(!self->enemy)
+	{
+//		gi.dprintf("player died, blowing up\n");
+		sphere_think_explode(self);
+	}
+}
+
+// *************************
+// Sphere Movement
+// *************************
+
+// =================
+// =================
+void sphere_fly (edict_t *self)
+{
+	vec3_t	dest;
+	vec3_t	dir;
+
+	if(level.time >= self->wait)
+	{
+//		gi.dprintf("fly: timed out\n");
+		sphere_think_explode(self);
+		return;
+	}
+
+	VectorCopy (self->owner->s.origin, dest);
+	dest[2] = self->owner->absmax[2] + 4;
+
+	if(level.time == (float)(int)level.time)
+	{
+		if(!visible(self, self->owner))
+		{
+			VectorCopy(dest, self->s.origin);
+			gi.linkentity(self);
+			return;
+		}
+	}
+
+	VectorSubtract (dest, self->s.origin, dir);
+	VectorScale (dir, 5, self->velocity);
+}
+
+// =================
+// =================
+void sphere_chase (edict_t *self, int stupidChase)
+{
+	vec3_t	dest;
+	vec3_t	dir;
+	float	dist;
+
+	if(level.time >= self->wait || (self->enemy && self->enemy->health < 1))
+	{
+		sphere_think_explode(self);
+		return;
+	}
+
+	VectorCopy (self->enemy->s.origin, dest);
+	if(self->enemy->client)
+		dest[2] += self->enemy->viewheight;
+
+	if(visible(self, self->enemy) || stupidChase)
+	{
+		// if moving, hunter sphere uses active sound
+		if(!stupidChase)
+			self->s.sound = gi.soundindex ("spheres/h_active.wav");
+
+		VectorSubtract (dest, self->s.origin, dir);
+		VectorNormalize (dir);
+		vectoangles2(dir, self->s.angles);
+		VectorScale (dir, 500, self->velocity);
+		VectorCopy(dest, self->monsterinfo.saved_goal);
+	}
+	else if (VectorCompare (self->monsterinfo.saved_goal, vec3_origin))
+	{
+		VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+		vectoangles2(dir, self->s.angles);
+
+		// if lurking, hunter sphere uses lurking sound
+		self->s.sound = gi.soundindex ("spheres/h_lurk.wav");
+		VectorClear (self->velocity);
+	}
+	else
+	{
+		VectorSubtract(self->monsterinfo.saved_goal, self->s.origin, dir);
+		dist = VectorNormalize(dir);
+
+		if(dist > 1)
+		{
+			vectoangles2(dir, self->s.angles);
+
+			if(dist > 500)			
+				VectorScale(dir, 500, self->velocity);
+			else if (dist < 20)
+				VectorScale(dir, (dist / FRAMETIME), self->velocity);
+			else
+				VectorScale(dir, dist, self->velocity);
+
+			// if moving, hunter sphere uses active sound
+			if(!stupidChase)
+				self->s.sound = gi.soundindex ("spheres/h_active.wav");
+		}
+		else
+		{
+			VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+			vectoangles2(dir, self->s.angles);
+
+			// if not moving, hunter sphere uses lurk sound
+			if(!stupidChase)
+				self->s.sound = gi.soundindex ("spheres/h_lurk.wav");
+
+			VectorClear(self->velocity);
+		}
+	}
+}
+
+// *************************
+// Attack related stuff
+// *************************
+
+// =================
+// =================
+void sphere_fire (edict_t *self, edict_t *enemy)
+{
+	vec3_t	dest;
+	vec3_t	dir;
+
+	if(level.time >= self->wait || !enemy)
+	{
+		sphere_think_explode(self);
+		return;
+	}
+
+	VectorCopy (enemy->s.origin, dest);
+	self->s.effects |= EF_ROCKET;
+
+	VectorSubtract (dest, self->s.origin, dir);
+	VectorNormalize (dir);
+	vectoangles2 ( dir, self->s.angles );
+	VectorScale (dir, 1000, self->velocity);
+
+	self->touch = vengeance_touch;
+	self->think = sphere_think_explode;
+	self->nextthink = self->wait;
+}
+
+// =================
+// =================
+void sphere_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf, int mod)
+{
+	if(self->spawnflags & SPHERE_DOPPLEGANGER)
+	{
+		if (other == self->teammaster)
+			return;
+
+		self->takedamage = DAMAGE_NO;
+		self->owner = self->teammaster;
+		self->teammaster = NULL;
+	}
+	else
+	{
+		if (other == self->owner)
+			return;
+		// PMM - don't blow up on bodies
+		if (!strcmp(other->classname, "bodyque"))
+			return;
+	}
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (other->takedamage)
+	{
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal,
+			10000, 1, DAMAGE_DESTROY_ARMOR, mod);
+	}
+	else
+	{
+		T_RadiusDamage (self, self->owner, 512, self->owner, 256, mod);
+	}
+
+	sphere_think_explode (self);
+}
+
+// =================
+// =================
+void vengeance_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if(self->spawnflags & SPHERE_DOPPLEGANGER)
+		sphere_touch (self, other, plane, surf, MOD_DOPPLE_VENGEANCE);
+	else
+		sphere_touch (self, other, plane, surf, MOD_VENGEANCE_SPHERE);
+}
+
+// =================
+// =================
+void hunter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	edict_t	*owner;
+
+	// don't blow up if you hit the world.... sheesh.
+	if(other==WORLD)
+		return;
+
+	if(self->owner)
+	{
+		// if owner is flying with us, make sure they stop too.
+		owner=self->owner;
+		if(owner->flags & FL_SAM_RAIMI)
+		{
+			VectorClear(owner->velocity);
+			owner->movetype = MOVETYPE_NONE;
+			gi.linkentity(owner);
+		}
+	}
+
+	if(self->spawnflags & SPHERE_DOPPLEGANGER)
+		sphere_touch (self, other, plane, surf, MOD_DOPPLE_HUNTER);
+	else
+		sphere_touch (self, other, plane, surf, MOD_HUNTER_SPHERE);
+}
+
+// =================
+// =================
+void defender_shoot (edict_t *self, edict_t *enemy)
+{
+	vec3_t	dir;
+	vec3_t	start;
+
+	if(!(enemy->inuse) || enemy->health <= 0)
+		return;
+
+	if(enemy == self->owner)
+		return;
+
+	VectorSubtract (enemy->s.origin, self->s.origin, dir);
+	VectorNormalize (dir);
+
+	if(self->monsterinfo.attack_finished > level.time)
+		return;
+
+	if(!visible(self, self->enemy))
+		return;
+
+	VectorCopy(self->s.origin, start);
+	start[2] += 2;
+	fire_blaster2 (self->owner, start, dir, 10, 1000, EF_BLASTER, 0);
+
+	self->monsterinfo.attack_finished = level.time + 0.4;
+}
+
+// *************************
+// Activation Related Stuff
+// *************************
+
+// =================
+// =================
+void body_gib (edict_t *self)
+{
+	int		n;
+
+	gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+	for (n= 0; n < 4; n++)
+		ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 50, GIB_ORGANIC);
+	ThrowGib (self, "models/objects/gibs/skull/tris.md2", 50, GIB_ORGANIC);
+}
+
+// =================
+// =================
+void hunter_pain (edict_t *self, edict_t *other, float, int)
+{
+	edict_t	*owner;
+	float	dist;
+	vec3_t	dir;
+
+	if(self->enemy)
+		return;
+
+	owner = self->owner;
+
+	if(!(self->spawnflags & SPHERE_DOPPLEGANGER))
+	{
+		if(owner && (owner->health > 0))
+			return;
+
+		//PMM
+		if(other == owner)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("hunter: won't get mad at my owner!\n");
+			return;
+		}
+		//pmm
+	}
+	else
+	{
+		// if fired by a doppleganger, set it to 10 second timeout
+		self->wait = level.time + MINIMUM_FLY_TIME;
+	}
+
+	if((self->wait - level.time) < MINIMUM_FLY_TIME)
+		self->wait = level.time + MINIMUM_FLY_TIME;
+	self->s.effects |= EF_BLASTER | EF_TRACKER;
+	self->touch = hunter_touch;
+	self->enemy = other;
+
+//	if(g_showlogic && g_showlogic->value)
+//		gi.dprintf("hunter_pain: mad at %s\n", other->classname);
+
+	// if we're not owned by a player, no sam raimi
+	// if we're spawned by a doppleganger, no sam raimi
+	if((self->spawnflags & SPHERE_DOPPLEGANGER)  || !(owner && owner->client))		
+		return;
+
+	// sam raimi cam is disabled if FORCE_RESPAWN is set.
+	// sam raimi cam is also disabled if huntercam->value is 0.
+	if(!((int)dmflags->value & DF_FORCE_RESPAWN) &&	(huntercam && (huntercam->value)))
+	{
+		VectorSubtract(other->s.origin, self->s.origin, dir);
+		dist=VectorLength(dir);
+
+		if(owner && (dist >= 192))
+		{
+			// detach owner from body and send him flying
+			owner->movetype = MOVETYPE_FLYMISSILE;
+
+			// gib like we just died, even though we didn't, really.
+			body_gib(owner);
+
+			// move the sphere to the owner's current viewpoint.
+			// we know it's a valid spot (or will be momentarily)
+			VectorCopy(owner->s.origin, self->s.origin);
+			self->s.origin[2] += owner->viewheight;
+
+			// move the player's origin to the sphere's new origin
+			VectorCopy(self->s.origin, owner->s.origin);
+			VectorCopy(self->s.angles, owner->s.angles);
+			VectorCopy(self->s.angles, owner->client->v_angle);
+			VectorClear(owner->mins);
+			VectorClear(owner->maxs);
+			VectorSet(owner->mins, -5, -5, -5);
+			VectorSet(owner->maxs, 5, 5, 5);
+			owner->client->ps.fov = 140;
+			owner->s.modelindex = 0;
+			owner->s.modelindex2 = 0;
+			owner->viewheight = 8;
+			owner->solid = SOLID_NOT;
+			owner->flags |= FL_SAM_RAIMI;
+			gi.linkentity(owner);
+
+			// PMM - set bounding box so we don't clip out of world
+//			VectorSet(self->mins, -5, -5, -5);
+//			VectorSet(self->maxs, 5, 5, 5);
+			self->solid = SOLID_BBOX;
+			gi.linkentity (self);
+		}
+//		else
+//			gi.dprintf("too close for sam raimi cam\n");
+	}
+}
+
+// =================
+// =================
+void defender_pain (edict_t *self, edict_t *other, float, int)
+{
+	//PMM
+	if(other == self->owner)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("defender: won't get mad at my owner!\n");
+		return;
+	}
+	//pmm
+	self->enemy = other;
+}
+
+// =================
+// =================
+void vengeance_pain (edict_t *self, edict_t *other, float, int)
+{
+	if(self->enemy)
+		return;
+
+	if(!(self->spawnflags & SPHERE_DOPPLEGANGER))
+	{
+		if(self->owner->health >= 25)
+			return;
+
+		//PMM
+		if(other == self->owner)
+		{
+	//		if ((g_showlogic) && (g_showlogic->value))
+	//			gi.dprintf ("vengeance: won't get mad at my owner!\n");
+			return;
+		}
+		//pmm
+	}
+	else
+	{
+		self->wait = level.time + MINIMUM_FLY_TIME;
+	}
+
+	if((self->wait - level.time) < MINIMUM_FLY_TIME)
+		self->wait = level.time + MINIMUM_FLY_TIME;
+	self->s.effects |= EF_ROCKET;
+	self->touch = vengeance_touch;
+	self->enemy = other;
+}
+
+// *************************
+// Think Functions
+// *************************
+
+// ===================
+// ===================
+void defender_think (edict_t *self)
+{
+	if(!self->owner)
+	{
+//		gi.dprintf("think: no owner\n");
+		G_FreeEdict(self);
+		return;
+	}
+
+	// if we've exited the level, just remove ourselves.
+	if (level.intermissiontime)
+	{	
+		sphere_think_explode(self);
+		return;
+	}	
+
+	if(self->owner->health <=0)
+	{
+		sphere_think_explode(self);
+		return;
+	}
+
+//	if(level.time - self->timestamp > 1)
+//	{
+//		gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/dsphere.wav"), 0.6, ATTN_NORM, 0);
+//		self->timestamp = level.time;
+//	}
+
+	self->s.frame++;
+	if(self->s.frame>19)
+		self->s.frame = 0;
+
+	if(self->enemy)
+	{
+		if(self->enemy->health > 0)
+		{
+//			gi.dprintf( "shooting at %s\n", self->enemy->classname);
+			defender_shoot (self, self->enemy);
+		}
+		else
+			self->enemy = NULL;
+	}
+//	else
+//	{
+//		self->ideal_yaw+=3;
+//		M_ChangeYaw (self);
+//	}
+
+	sphere_fly (self);
+
+	if(self->inuse)
+		self->nextthink = level.time + 0.1;
+}
+
+// =================
+// =================
+void hunter_think (edict_t *self)
+{
+	edict_t *owner;
+	vec3_t	dir, ang;
+
+	// if we've exited the level, just remove ourselves.
+	if (level.intermissiontime)
+	{	
+		sphere_think_explode(self);
+		return;
+	}	
+
+	owner = self->owner;
+	if(!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER))
+	{
+//		gi.dprintf("think: no owner\n");
+		G_FreeEdict(self);
+		return;
+	}
+
+	if(owner)
+		self->ideal_yaw = owner->s.angles[YAW];
+	else if(self->enemy)		// fired by doppleganger
+	{
+		VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+		vectoangles2(dir, ang);
+		self->ideal_yaw = ang[YAW];
+	}
+
+	M_ChangeYaw(self);
+
+//	if(level.time - self->timestamp > 1)
+//	{
+//		gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/hsphere.wav"), 0.5, ATTN_NORM, 0);
+//		self->timestamp = level.time;
+//	}
+
+	if(self->enemy)
+	{
+		sphere_chase (self, 0);
+
+		// deal with sam raimi cam
+		if(owner && (owner->flags & FL_SAM_RAIMI)) 
+		{
+			if(self->inuse)
+			{
+				owner->movetype = MOVETYPE_FLYMISSILE;
+//				VectorCopy(self->s.angles, owner->s.angles);
+//				VectorCopy(self->s.angles, owner->client->v_angle);
+				LookAtKiller (owner, self, self->enemy);
+//				owner->viewheight = 22;
+
+//				owner->client->v_angle[YAW]+=5;
+				// owner is flying with us, move him too
+				owner->movetype = MOVETYPE_FLYMISSILE;
+				owner->viewheight = self->s.origin[2] - owner->s.origin[2];
+//				VectorCopy(self->s.angles, owner->s.angles);
+//				VectorCopy(self->s.angles, owner->client->v_angle);
+				VectorCopy(self->s.origin, owner->s.origin);
+				VectorCopy(self->velocity, owner->velocity);
+				VectorClear(owner->mins);
+				VectorClear(owner->maxs);
+				gi.linkentity(owner);
+			}
+			else	// sphere timed out
+			{
+				VectorClear(owner->velocity);
+				owner->movetype = MOVETYPE_NONE;
+				gi.linkentity(owner);
+			}
+		}
+	}
+	else 
+	{
+//		self->ideal_yaw+=3;
+//		M_ChangeYaw (self);
+		sphere_fly (self);
+	}
+
+	if(self->inuse)
+		self->nextthink = level.time + 0.1;
+}
+
+// =================
+// =================
+void vengeance_think (edict_t *self)
+{
+	// if we've exited the level, just remove ourselves.
+	if (level.intermissiontime)
+	{	
+		sphere_think_explode(self);
+		return;
+	}	
+
+	if(!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER))
+	{
+//		gi.dprintf("think: no owner\n");
+		G_FreeEdict(self);
+		return;
+	}
+
+//	if(level.time - self->timestamp > 1)
+//	{
+//		gi.sound (self, CHAN_VOICE, gi.soundindex ("powerup/vsphere.wav"), 0.5, ATTN_NORM, 0);
+//		self->timestamp = level.time;
+//	}
+
+	if(self->enemy)
+	{
+//		sphere_fire (self, self->owner->enemy);
+		sphere_chase (self, 1);
+	}
+	else
+		sphere_fly (self);
+
+	if(self->inuse)
+		self->nextthink = level.time + 0.1;
+}
+
+// *************************
+// Spawning / Creation
+// *************************
+
+// monsterinfo_t
+// =================
+// =================
+edict_t *Sphere_Spawn (edict_t *owner, int spawnflags)
+{
+	edict_t *sphere;
+
+	sphere = G_Spawn();
+	VectorCopy(owner->s.origin, sphere->s.origin);
+	sphere->s.origin[2] = owner->absmax[2];
+	sphere->s.angles[YAW] = owner->s.angles[YAW];
+	sphere->solid = SOLID_BBOX;
+	sphere->clipmask = MASK_SHOT;
+	sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
+	sphere->movetype = MOVETYPE_FLYMISSILE;
+
+	if(spawnflags & SPHERE_DOPPLEGANGER)
+		sphere->teammaster = owner->teammaster;
+	else
+		sphere->owner = owner;
+	
+	sphere->classname = "sphere";
+	sphere->yaw_speed = 40;
+	sphere->monsterinfo.attack_finished = 0;
+	sphere->spawnflags = spawnflags;		// need this for the HUD to recognize sphere
+	//PMM
+	sphere->takedamage = DAMAGE_NO;
+
+	switch(spawnflags & SPHERE_TYPE)
+	{
+		case SPHERE_DEFENDER:
+			sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2");
+			// PMM - this doesn't work, causes problems with other stuff
+//			sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2") | 0x80;
+			sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2");
+			sphere->s.sound = gi.soundindex ("spheres/d_idle.wav");
+			sphere->pain = defender_pain;
+			sphere->wait = level.time + DEFENDER_LIFESPAN;
+			sphere->die = sphere_explode;
+			sphere->think = defender_think;
+			break;
+		case SPHERE_HUNTER:
+			sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2");
+			sphere->s.sound = gi.soundindex ("spheres/h_idle.wav");
+			sphere->wait = level.time + HUNTER_LIFESPAN;
+			sphere->pain = hunter_pain;
+			sphere->die = sphere_if_idle_die;
+			sphere->think = hunter_think;
+			break;
+		case SPHERE_VENGEANCE:
+			sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2");
+			sphere->s.sound = gi.soundindex ("spheres/v_idle.wav");
+			sphere->wait = level.time + VENGEANCE_LIFESPAN;
+			sphere->pain = vengeance_pain;
+			sphere->die = sphere_if_idle_die;
+			sphere->think = vengeance_think;
+			VectorSet (sphere->avelocity, 30, 30, 0);
+			break;
+		default:
+			gi.dprintf("Tried to create an invalid sphere\n");
+			G_FreeEdict(sphere);
+			return NULL;
+	}
+	
+	sphere->nextthink = level.time + 0.1;
+
+	gi.linkentity (sphere);
+
+	return sphere;
+}
+
+// =================
+// Own_Sphere - attach the sphere to the client so we can 
+//		directly access it later
+// =================
+void Own_Sphere (edict_t *self, edict_t *sphere)
+{
+	if(!sphere)
+		return;
+
+	// ownership only for players	
+	if(self->client)
+	{
+		// if they don't have one
+		if(!(self->client->owned_sphere))
+		{
+			self->client->owned_sphere = sphere;
+		}
+		// they already have one, take care of the old one
+		else
+		{
+			if(self->client->owned_sphere->inuse)
+			{
+				G_FreeEdict(self->client->owned_sphere);
+				self->client->owned_sphere = sphere;
+			}
+			else
+			{
+				self->client->owned_sphere = sphere;
+			}
+		}
+	}
+}
+
+// =================
+// =================
+void Defender_Launch (edict_t *self)
+{
+	edict_t		*sphere;
+
+	sphere = Sphere_Spawn (self, SPHERE_DEFENDER);	
+	Own_Sphere (self, sphere);
+}
+
+// =================
+// =================
+void Hunter_Launch (edict_t *self)
+{
+	edict_t		*sphere;
+
+	sphere = Sphere_Spawn (self, SPHERE_HUNTER);	
+	Own_Sphere (self, sphere);
+}
+
+// =================
+// =================
+void Vengeance_Launch (edict_t *self)
+{
+	edict_t		*sphere;
+
+	sphere = Sphere_Spawn (self, SPHERE_VENGEANCE);	
+	Own_Sphere (self, sphere);
+}
--- /dev/null
+++ b/rogue/g_svcmds.c
@@ -1,0 +1,284 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void	Svcmd_Test_f (void)
+{
+	gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
+}
+
+/*
+==============================================================================
+
+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 any unspecified digits will 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.
+
+writeip
+Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date.  The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
+
+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.
+
+
+==============================================================================
+*/
+
+typedef struct
+{
+	unsigned	mask;
+	unsigned	compare;
+} ipfilter_t;
+
+#define	MAX_IPFILTERS	1024
+
+ipfilter_t	ipfilters[MAX_IPFILTERS];
+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')
+		{
+			gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
+			return false;
+		}
+		
+		j = 0;
+		while (*s >= '0' && *s <= '9')
+		{
+			num[j++] = *s++;
+		}
+		num[j] = 0;
+		b[i] = atoi(num);
+		if (b[i] != 0)
+			m[i] = 255;
+
+		if (!*s)
+			break;
+		s++;
+	}
+	
+	f->mask = *(unsigned *)m;
+	f->compare = *(unsigned *)b;
+	
+	return true;
+}
+
+/*
+=================
+SV_FilterPacket
+=================
+*/
+qboolean SV_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 (int)filterban->value;
+
+	return (int)!filterban->value;
+}
+
+
+/*
+=================
+SV_AddIP_f
+=================
+*/
+void SVCmd_AddIP_f (void)
+{
+	int		i;
+	
+	if (gi.argc() < 3) {
+		gi.cprintf(NULL, PRINT_HIGH, "Usage:  addip <ip-mask>\n");
+		return;
+	}
+
+	for (i=0 ; i<numipfilters ; i++)
+		if (ipfilters[i].compare == 0xffffffff)
+			break;		// free spot
+	if (i == numipfilters)
+	{
+		if (numipfilters == MAX_IPFILTERS)
+		{
+			gi.cprintf (NULL, PRINT_HIGH, "IP filter list is full\n");
+			return;
+		}
+		numipfilters++;
+	}
+	
+	if (!StringToFilter (gi.argv(2), &ipfilters[i]))
+		ipfilters[i].compare = 0xffffffff;
+}
+
+/*
+=================
+SV_RemoveIP_f
+=================
+*/
+void SVCmd_RemoveIP_f (void)
+{
+	ipfilter_t	f;
+	int			i, j;
+
+	if (gi.argc() < 3) {
+		gi.cprintf(NULL, PRINT_HIGH, "Usage:  sv removeip <ip-mask>\n");
+		return;
+	}
+
+	if (!StringToFilter (gi.argv(2), &f))
+		return;
+
+	for (i=0 ; i<numipfilters ; i++)
+		if (ipfilters[i].mask == f.mask
+		&& ipfilters[i].compare == f.compare)
+		{
+			for (j=i+1 ; j<numipfilters ; j++)
+				ipfilters[j-1] = ipfilters[j];
+			numipfilters--;
+			gi.cprintf (NULL, PRINT_HIGH, "Removed.\n");
+			return;
+		}
+	gi.cprintf (NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
+}
+
+/*
+=================
+SV_ListIP_f
+=================
+*/
+void SVCmd_ListIP_f (void)
+{
+	int		i;
+	byte	b[4];
+
+	gi.cprintf (NULL, PRINT_HIGH, "Filter list:\n");
+	for (i=0 ; i<numipfilters ; i++)
+	{
+		*(unsigned *)b = ipfilters[i].compare;
+		gi.cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
+	}
+}
+
+/*
+=================
+SV_WriteIP_f
+=================
+*/
+void SVCmd_WriteIP_f (void)
+{
+	FILE	*f;
+	char	name[MAX_OSPATH];
+	byte	b[4];
+	int		i;
+	cvar_t	*game;
+
+	game = gi.cvar("game", "", 0);
+
+	if (!*game->string)
+		sprintf (name, "%s/listip.cfg", GAMEVERSION);
+	else
+		sprintf (name, "%s/listip.cfg", game->string);
+
+	gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name);
+
+	f = fopen (name, "wb");
+	if (!f)
+	{
+		gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name);
+		return;
+	}
+	
+	fprintf(f, "set filterban %d\n", (int)filterban->value);
+
+	for (i=0 ; i<numipfilters ; i++)
+	{
+		*(unsigned *)b = ipfilters[i].compare;
+		fprintf (f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
+	}
+	
+	fclose (f);
+}
+
+/*
+=================
+ServerCommand
+
+ServerCommand will be called when an "sv" command is issued.
+The game can issue gi.argc() / gi.argv() commands to get the rest
+of the parameters
+=================
+*/
+void	ServerCommand (void)
+{
+	char	*cmd;
+
+	cmd = gi.argv(1);
+	if (cistrcmp (cmd, "test") == 0)
+		Svcmd_Test_f ();
+	else if (cistrcmp (cmd, "addip") == 0)
+		SVCmd_AddIP_f ();
+	else if (cistrcmp (cmd, "removeip") == 0)
+		SVCmd_RemoveIP_f ();
+	else if (cistrcmp (cmd, "listip") == 0)
+		SVCmd_ListIP_f ();
+	else if (cistrcmp (cmd, "writeip") == 0)
+		SVCmd_WriteIP_f ();
+	else
+		gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
+}
+
--- /dev/null
+++ b/rogue/g_target.c
@@ -1,0 +1,831 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
+Fire an origin based temp entity event to the clients.
+"style"		type byte
+*/
+void Use_Target_Tent (edict_t *ent, edict_t *, edict_t *)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (ent->style);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+}
+
+void SP_target_temp_entity (edict_t *ent)
+{
+	ent->use = Use_Target_Tent;
+}
+
+
+//==========================================================
+
+//==========================================================
+
+/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
+"noise"		wav file to play
+"attenuation"
+-1 = none, send to whole level
+1 = normal fighting sounds
+2 = idle sound level
+3 = ambient sound level
+"volume"	0.0 to 1.0
+
+Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.
+
+Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
+Multiple identical looping sounds will just increase volume without any speed cost.
+*/
+void Use_Target_Speaker (edict_t *ent, edict_t *, edict_t *)
+{
+	int		chan;
+
+	if (ent->spawnflags & 3)
+	{	// looping sound toggles
+		if (ent->s.sound)
+			ent->s.sound = 0;	// turn it off
+		else
+			ent->s.sound = ent->noise_index;	// start it
+	}
+	else
+	{	// normal sound
+		if (ent->spawnflags & 4)
+			chan = CHAN_VOICE|CHAN_RELIABLE;
+		else
+			chan = CHAN_VOICE;
+		// use a positioned_sound, because this entity won't normally be
+		// sent to any clients because it is invisible
+		gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
+	}
+}
+
+void SP_target_speaker (edict_t *ent)
+{
+	char	buffer[MAX_QPATH];
+
+	if(!st.noise)
+	{
+		gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
+		return;
+	}
+	if (!strstr (st.noise, ".wav"))
+		Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
+	else
+		strncpy (buffer, st.noise, sizeof(buffer));
+	ent->noise_index = gi.soundindex (buffer);
+
+	if (!ent->volume)
+		ent->volume = 1.0;
+
+	if (!ent->attenuation)
+		ent->attenuation = 1.0;
+	else if (ent->attenuation == -1)	// use -1 so 0 defaults to 1
+		ent->attenuation = 0;
+
+	// check for prestarted looping sound
+	if (ent->spawnflags & 1)
+		ent->s.sound = ent->noise_index;
+
+	ent->use = Use_Target_Speaker;
+
+	// must link the entity so we get areas and clusters so
+	// the server can determine who to send updates to
+	gi.linkentity (ent);
+}
+
+
+//==========================================================
+
+void Use_Target_Help (edict_t *ent, edict_t *, edict_t *)
+{
+	if (ent->spawnflags & 1)
+		strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
+	else
+		strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
+
+	game.helpchanged++;
+}
+
+/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
+When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
+*/
+void SP_target_help(edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->message)
+	{
+		gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+	ent->use = Use_Target_Help;
+}
+
+//==========================================================
+
+/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a secret found.
+These are single use targets.
+*/
+void use_target_secret (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_secrets++;
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_secret (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_secret;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_secrets++;
+	// map bug hack
+	if (!cistrcmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
+		ent->message = "You have found a secret area.";
+}
+
+//==========================================================
+
+/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a goal completed.
+These are single use targets.
+*/
+void use_target_goal (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_goals++;
+
+	if (level.found_goals == level.total_goals)
+		gi.configstring (CS_CDTRACK, "0");
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_goal (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_goal;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_goals++;
+}
+
+//==========================================================
+
+
+/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
+Spawns an explosion temporary entity when used.
+
+"delay"		wait this long before going off
+"dmg"		how much radius damage should be done, defaults to 0
+*/
+void target_explosion_explode (edict_t *self)
+{
+	float		save;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	save = self->delay;
+	self->delay = 0;
+	G_UseTargets (self, self->activator);
+	self->delay = save;
+}
+
+void use_target_explosion (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (!self->delay)
+	{
+		target_explosion_explode (self);
+		return;
+	}
+
+	self->think = target_explosion_explode;
+	self->nextthink = level.time + self->delay;
+}
+
+void SP_target_explosion (edict_t *ent)
+{
+	ent->use = use_target_explosion;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
+Changes level to "map" when fired
+*/
+void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
+{
+	if (level.intermissiontime)
+		return;		// already activated
+
+	if (!deathmatch->value && !coop->value)
+	{
+		if (g_edicts[1].health <= 0)
+			return;
+	}
+
+	// if noexit, do a ton of damage to other
+	if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != WORLD)
+	{
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
+		return;
+	}
+
+	// if multiplayer, let everyone know who hit the exit
+	if (deathmatch->value)
+	{
+		if (activator && activator->client)
+			gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
+	}
+
+	// if going to a new unit, clear cross triggers
+	if (strstr(self->map, "*"))	
+		game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
+
+	BeginIntermission (self);
+}
+
+void SP_target_changelevel (edict_t *ent)
+{
+	if (!ent->map)
+	{
+		gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	// ugly hack because *SOMEBODY* screwed up their map
+   if((cistrcmp(level.mapname, "fact1") == 0) && (cistrcmp(ent->map, "fact3") == 0))
+	   ent->map = "fact3$secret1";
+
+	ent->use = use_target_changelevel;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
+Creates a particle splash effect when used.
+
+Set "sounds" to one of the following:
+  1) sparks
+  2) blue water
+  3) brown water
+  4) slime
+  5) lava
+  6) blood
+
+"count"	how many pixels in the splash
+"dmg"	if set, does a radius damage at this location when it splashes
+		useful for lava/sparks
+*/
+
+void use_target_splash (edict_t *self, edict_t *, edict_t *activator)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPLASH);
+	gi.WriteByte (self->count);
+	gi.WritePosition (self->s.origin);
+	gi.WriteDir (self->movedir);
+	gi.WriteByte (self->sounds);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	if (self->dmg)
+		T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
+}
+
+void SP_target_splash (edict_t *self)
+{
+	self->use = use_target_splash;
+	G_SetMovedir (self->s.angles, self->movedir);
+
+	if (!self->count)
+		self->count = 32;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
+Set target to the type of entity you want spawned.
+Useful for spawning monsters and gibs in the factory levels.
+
+For monsters:
+	Set direction to the facing you want it to have.
+
+For gibs:
+	Set direction if you want it moving and
+	speed how fast it should be moving otherwise it
+	will just be dropped
+*/
+void ED_CallSpawn (edict_t *ent);
+
+void use_target_spawner (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t	*ent;
+
+	ent = G_Spawn();
+	ent->classname = self->target;
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (self->s.angles, ent->s.angles);
+	ED_CallSpawn (ent);
+	gi.unlinkentity (ent);
+	KillBox (ent);
+	gi.linkentity (ent);
+	if (self->speed)
+		VectorCopy (self->movedir, ent->velocity);
+
+	ent->s.renderfx |= RF_IR_VISIBLE;		//PGM
+}
+
+void SP_target_spawner (edict_t *self)
+{
+	self->use = use_target_spawner;
+	self->svflags = SVF_NOCLIENT;
+	if (self->speed)
+	{
+		G_SetMovedir (self->s.angles, self->movedir);
+		VectorScale (self->movedir, self->speed, self->movedir);
+	}
+}
+
+//==========================================================
+
+/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
+Fires a blaster bolt in the set direction when triggered.
+
+dmg		default is 15
+speed	default is 1000
+*/
+
+void use_target_blaster (edict_t *self, edict_t *, edict_t *)
+{
+	/*
+	int effect;
+
+	if (self->spawnflags & 2)
+		effect = 0;
+	else if (self->spawnflags & 1)
+		effect = EF_HYPERBLASTER;
+	else
+		effect = EF_BLASTER;
+	*/
+
+	fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
+	gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
+}
+
+void SP_target_blaster (edict_t *self)
+{
+	self->use = use_target_blaster;
+	G_SetMovedir (self->s.angles, self->movedir);
+	self->noise_index = gi.soundindex ("weapons/laser2.wav");
+
+	if (!self->dmg)
+		self->dmg = 15;
+	if (!self->speed)
+		self->speed = 1000;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit.  It is OK to check multiple triggers.  Message, delay, target, and killtarget also work.
+*/
+void trigger_crosslevel_trigger_use (edict_t *self, edict_t *, edict_t *)
+{
+	game.serverflags |= self->spawnflags;
+	G_FreeEdict (self);
+}
+
+void SP_target_crosslevel_trigger (edict_t *self)
+{
+	self->svflags = SVF_NOCLIENT;
+	self->use = trigger_crosslevel_trigger_use;
+}
+
+/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
+killtarget also work.
+
+"delay"		delay before using targets if the trigger has been activated (default 1)
+*/
+void target_crosslevel_target_think (edict_t *self)
+{
+	if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
+	{
+		G_UseTargets (self, self);
+		G_FreeEdict (self);
+	}
+}
+
+void SP_target_crosslevel_target (edict_t *self)
+{
+	if (! self->delay)
+		self->delay = 1;
+	self->svflags = SVF_NOCLIENT;
+
+	self->think = target_crosslevel_target_think;
+	self->nextthink = level.time + self->delay;
+}
+
+//==========================================================
+
+/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP
+When triggered, fires a laser.  You can either set a target
+or a direction.
+
+WINDOWSTOP - stops at CONTENTS_WINDOW
+*/
+
+//======
+// PGM
+#define LASER_ON			0x0001
+#define LASER_RED			0x0002
+#define LASER_GREEN			0x0004
+#define LASER_BLUE			0x0008
+#define LASER_YELLOW		0x0010
+#define LASER_ORANGE		0x0020
+#define LASER_FAT			0x0040
+#define LASER_STOPWINDOW	0x0080
+// PGM
+//======
+
+void target_laser_think (edict_t *self)
+{
+	edict_t	*ignore;
+	vec3_t	start;
+	vec3_t	end;
+	trace_t	tr;
+	vec3_t	point;
+	vec3_t	last_movedir;
+	int		count;
+
+	if (self->spawnflags & 0x80000000)
+		count = 8;
+	else
+		count = 4;
+
+	if (self->enemy)
+	{
+		VectorCopy (self->movedir, last_movedir);
+		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
+		VectorSubtract (point, self->s.origin, self->movedir);
+		VectorNormalize (self->movedir);
+		if (!VectorCompare(self->movedir, last_movedir))
+			self->spawnflags |= 0x80000000;
+	}
+
+	ignore = self;
+	VectorCopy (self->s.origin, start);
+	VectorMA (start, 2048, self->movedir, end);
+	while(1)
+	{
+//======
+// PGM
+		if(self->spawnflags & LASER_STOPWINDOW)
+			tr = gi.trace (start, NULL, NULL, end, ignore, MASK_SHOT);
+		else
+			tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+// PGM
+//======
+
+		if (!tr.ent)
+			break;
+
+		// hurt it if we can
+		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
+			T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
+
+		// if we hit something that's not a monster or player or is immune to lasers, we're done
+//		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		//PMM added SVF_DAMAGEABLE
+		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && !(tr.ent->svflags & SVF_DAMAGEABLE))
+		{
+			if (self->spawnflags & 0x80000000)
+			{
+				self->spawnflags &= ~0x80000000;
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (count);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+			}
+			break;
+		}
+
+		ignore = tr.ent;
+		VectorCopy (tr.endpos, start);
+	}
+
+	VectorCopy (tr.endpos, self->s.old_origin);
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void target_laser_on (edict_t *self)
+{
+	if (!self->activator)
+		self->activator = self;
+	self->spawnflags |= 0x80000001;
+	self->svflags &= ~SVF_NOCLIENT;
+	target_laser_think (self);
+}
+
+void target_laser_off (edict_t *self)
+{
+	self->spawnflags &= ~1;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+}
+
+void target_laser_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	if (self->spawnflags & 1)
+		target_laser_off (self);
+	else
+		target_laser_on (self);
+}
+
+void target_laser_start (edict_t *self)
+{
+	edict_t *ent;
+
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
+	self->s.modelindex = 1;			// must be non-zero
+
+	// set the beam diameter
+	if (self->spawnflags & 64)
+		self->s.frame = 16;
+	else
+		self->s.frame = 4;
+
+	// set the color
+	if (self->spawnflags & 2)
+		self->s.skinnum = 0xf2f2f0f0;
+	else if (self->spawnflags & 4)
+		self->s.skinnum = 0xd0d1d2d3;
+	else if (self->spawnflags & 8)
+		self->s.skinnum = 0xf3f3f1f1;
+	else if (self->spawnflags & 16)
+		self->s.skinnum = 0xdcdddedf;
+	else if (self->spawnflags & 32)
+		self->s.skinnum = 0xe0e1e2e3;
+
+	if (!self->enemy)
+	{
+		if (self->target)
+		{
+			ent = G_Find (NULL, FOFS(targetname), self->target);
+			if (!ent)
+				gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
+			self->enemy = ent;
+		}
+		else
+		{
+			G_SetMovedir (self->s.angles, self->movedir);
+		}
+	}
+	self->use = target_laser_use;
+	self->think = target_laser_think;
+
+	if (!self->dmg)
+		self->dmg = 1;
+
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	gi.linkentity (self);
+
+	if (self->spawnflags & 1)
+		target_laser_on (self);
+	else
+		target_laser_off (self);
+}
+
+void SP_target_laser (edict_t *self)
+{
+	// let everything else get spawned before we start firing
+	self->think = target_laser_start;
+	self->nextthink = level.time + 1;
+}
+
+//==========================================================
+
+/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
+speed		How many seconds the ramping will take
+message		two letters; starting lightlevel and ending lightlevel
+*/
+
+void target_lightramp_think (edict_t *self)
+{
+	char	style[2];
+
+	style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
+	style[1] = 0;
+	gi.configstring (CS_LIGHTS+self->enemy->style, style);
+
+	if ((level.time - self->timestamp) < self->speed)
+	{
+		self->nextthink = level.time + FRAMETIME;
+	}
+	else if (self->spawnflags & 1)
+	{
+		char	temp;
+
+		temp = self->movedir[0];
+		self->movedir[0] = self->movedir[1];
+		self->movedir[1] = temp;
+		self->movedir[2] *= -1;
+	}
+}
+
+void target_lightramp_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!self->enemy)
+	{
+		edict_t		*e;
+
+		// check all the targets
+		e = NULL;
+		while (1)
+		{
+			e = G_Find (e, FOFS(targetname), self->target);
+			if (!e)
+				break;
+			if (strcmp(e->classname, "light") != 0)
+			{
+				gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
+				gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
+			}
+			else
+			{
+				self->enemy = e;
+			}
+		}
+
+		if (!self->enemy)
+		{
+			gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
+			G_FreeEdict (self);
+			return;
+		}
+	}
+
+	self->timestamp = level.time;
+	target_lightramp_think (self);
+}
+
+void SP_target_lightramp (edict_t *self)
+{
+	if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
+	{
+		gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->svflags |= SVF_NOCLIENT;
+	self->use = target_lightramp_use;
+	self->think = target_lightramp_think;
+
+	self->movedir[0] = self->message[0] - 'a';
+	self->movedir[1] = self->message[1] - 'a';
+	self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
+}
+
+//==========================================================
+
+/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT
+When triggered, this initiates a level-wide earthquake.
+All players and monsters are affected.
+"speed"		severity of the quake (default:200)
+"count"		duration of the quake (default:5)
+*/
+
+void target_earthquake_think (edict_t *self)
+{
+	int		i;
+	edict_t	*e;
+
+	if(!(self->spawnflags & 1))					// PGM
+	{											// PGM
+		if (self->last_move_time < level.time)
+		{
+			gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
+			self->last_move_time = level.time + 0.5;
+		}
+	}											// PGM
+
+	for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->client)
+			continue;
+		if (!e->groundentity)
+			continue;
+
+		e->groundentity = NULL;
+		e->velocity[0] += crandom()* 150;
+		e->velocity[1] += crandom()* 150;
+		e->velocity[2] = self->speed * (100.0 / e->mass);
+	}
+
+	if (level.time < self->timestamp)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void target_earthquake_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	// PGM
+//	if(g_showlogic && g_showlogic->value)
+//		gi.dprintf("earthquake: %0.1f\n", self->speed);
+	// PGM
+
+	self->timestamp = level.time + self->count;
+	self->nextthink = level.time + FRAMETIME;
+	self->activator = activator;
+	self->last_move_time = 0;
+}
+
+void SP_target_earthquake (edict_t *self)
+{
+	if (!self->targetname)
+		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->count)
+		self->count = 5;
+
+	if (!self->speed)
+		self->speed = 200;
+
+	self->svflags |= SVF_NOCLIENT;
+	self->think = target_earthquake_think;
+	self->use = target_earthquake_use;
+
+	if(!(self->spawnflags & 1))									// PGM
+		self->noise_index = gi.soundindex ("world/quake.wav");
+}
--- /dev/null
+++ b/rogue/g_trigger.c
@@ -1,0 +1,674 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+//PGM - some of these are mine, some id's. I added the define's.
+#define TRIGGER_MONSTER		0x01
+#define TRIGGER_NOT_PLAYER	0x02
+#define TRIGGER_TRIGGERED	0x04
+#define TRIGGER_TOGGLE		0x08
+//PGM
+
+void InitTrigger (edict_t *self)
+{
+	if (!VectorCompare (self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+	gi.setmodel (self, self->model);
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+// the wait time has passed, so set back up for another activation
+void multi_wait (edict_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 (edict_t *ent)
+{
+	if (ent->nextthink)
+		return;		// already been triggered
+
+	G_UseTargets (ent, ent->activator);
+
+	if (ent->wait > 0)	
+	{
+		ent->think = multi_wait;
+		ent->nextthink = level.time + ent->wait;
+	}
+	else
+	{	// we can't just remove (self) here, because this is a touch function
+		// called while looping through area links...
+		ent->touch = NULL;
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = G_FreeEdict;
+	}
+}
+
+void Use_Multi (edict_t *ent, edict_t *, edict_t *activator)
+{
+//PGM
+	if(ent->spawnflags & TRIGGER_TOGGLE)
+	{
+		if(ent->solid == SOLID_TRIGGER)
+			ent->solid = SOLID_NOT;
+		else
+			ent->solid = SOLID_TRIGGER;
+		gi.linkentity (ent);
+	}
+	else
+	{
+		ent->activator = activator;
+		multi_trigger (ent);
+	}
+//PGM
+}
+
+void Touch_Multi (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if(other->client)
+	{
+		if (self->spawnflags & 2)
+			return;
+	}
+	else if (other->svflags & SVF_MONSTER)
+	{
+		if (!(self->spawnflags & 1))
+			return;
+	}
+	else
+		return;
+
+	if (!VectorCompare(self->movedir, vec3_origin))
+	{
+		vec3_t	forward;
+
+		AngleVectors(other->s.angles, forward, NULL, NULL);
+		if (_DotProduct(forward, self->movedir) < 0)
+			return;
+	}
+
+	self->activator = other;
+	multi_trigger (self);
+}
+
+/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE
+Variable sized repeatable trigger.  Must be targeted at one or more entities.
+If "delay" is set, the trigger waits some time after activating before firing.
+"wait" : Seconds between triggerings. (.2 default)
+
+TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive.
+
+sounds
+1)	secret
+2)	beep beep
+3)	large switch
+4)
+set "message" to text string
+*/
+void trigger_enable (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_TRIGGER;
+	self->use = Use_Multi;
+	gi.linkentity (self);
+}
+
+void SP_trigger_multiple (edict_t *ent)
+{
+	if (ent->sounds == 1)
+		ent->noise_index = gi.soundindex ("misc/secret.wav");
+	else if (ent->sounds == 2)
+		ent->noise_index = gi.soundindex ("misc/talk.wav");
+	else if (ent->sounds == 3)
+		ent->noise_index = gi.soundindex ("misc/trigger1.wav");
+	
+	if (!ent->wait)
+		ent->wait = 0.2;
+	ent->touch = Touch_Multi;
+	ent->movetype = MOVETYPE_NONE;
+	ent->svflags |= SVF_NOCLIENT;
+
+//PGM
+	if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE))
+	{
+		ent->solid = SOLID_NOT;
+		ent->use = trigger_enable;
+	}
+	else
+	{
+		ent->solid = SOLID_TRIGGER;
+		ent->use = Use_Multi;
+	}
+//PGM
+
+	if (!VectorCompare(ent->s.angles, vec3_origin))
+		G_SetMovedir (ent->s.angles, ent->movedir);
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
+Triggers once, then removes itself.
+You must set the key "target" to the name of another object in the level that has a matching "targetname".
+
+If TRIGGERED, this trigger must be triggered before it is live.
+
+sounds
+ 1)	secret
+ 2)	beep beep
+ 3)	large switch
+ 4)
+
+"message"	string to be displayed when triggered
+*/
+
+void SP_trigger_once(edict_t *ent)
+{
+	// make old maps work because I messed up on flag assignments here
+	// triggered was on bit 1 when it should have been on bit 4
+	if (ent->spawnflags & 1)
+	{
+		vec3_t	v;
+
+		VectorMA (ent->mins, 0.5, ent->size, v);
+		ent->spawnflags &= ~1;
+		ent->spawnflags |= 4;
+		gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
+	}
+
+	ent->wait = -1;
+	SP_trigger_multiple (ent);
+}
+
+/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This fixed size trigger cannot be touched, it can only be fired by other events.
+*/
+void trigger_relay_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	G_UseTargets (self, activator);
+}
+
+void SP_trigger_relay (edict_t *self)
+{
+	self->use = trigger_relay_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_key
+
+==============================================================================
+*/
+
+/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
+A relay trigger that only fires it's targets if player has the proper key.
+Use "item" to specify the required key, for example "key_data_cd"
+*/
+void trigger_key_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	int			index;
+
+	if (!self->item)
+		return;
+	if (!activator->client)
+		return;
+
+	index = ITEM_INDEX(self->item);
+	if (!activator->client->pers.inventory[index])
+	{
+		if (level.time < self->touch_debounce_time)
+			return;
+		self->touch_debounce_time = level.time + 5.0;
+		gi.centerprintf (activator, "You need the %s", self->item->pickup_name);
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0);
+		return;
+	}
+
+	gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0);
+	if (coop->value)
+	{
+		int		player;
+		edict_t	*ent;
+
+		if (strcmp(self->item->classname, "key_power_cube") == 0)
+		{
+			int	cube;
+
+			for (cube = 0; cube < 8; cube++)
+				if (activator->client->pers.power_cubes & (1 << cube))
+					break;
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				if (ent->client->pers.power_cubes & (1 << cube))
+				{
+					ent->client->pers.inventory[index]--;
+					ent->client->pers.power_cubes &= ~(1 << cube);
+				}
+			}
+		}
+		else
+		{
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				ent->client->pers.inventory[index] = 0;
+			}
+		}
+	}
+	else
+	{
+		activator->client->pers.inventory[index]--;
+	}
+
+	G_UseTargets (self, activator);
+
+	self->use = NULL;
+}
+
+void SP_trigger_key (edict_t *self)
+{
+	if (!st.item)
+	{
+		gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
+		return;
+	}
+	self->item = FindItemByClassname (st.item);
+
+	if (!self->item)
+	{
+		gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin));
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin));
+		return;
+	}
+
+	gi.soundindex ("misc/keytry.wav");
+	gi.soundindex ("misc/keyuse.wav");
+
+	self->use = trigger_key_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_counter
+
+==============================================================================
+*/
+
+/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
+Acts as an intermediary for an action that takes multiple inputs.
+
+If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
+*/
+
+void trigger_counter_use(edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->count == 0)
+		return;
+	
+	self->count--;
+
+	if (self->count)
+	{
+		if (! (self->spawnflags & 1))
+		{
+			gi.centerprintf(activator, "%i more to go...", self->count);
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+	
+	if (! (self->spawnflags & 1))
+	{
+		gi.centerprintf(activator, "Sequence completed!");
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+	self->activator = activator;
+	multi_trigger (self);
+}
+
+void SP_trigger_counter (edict_t *self)
+{
+	self->wait = -1;
+	if (!self->count)
+		self->count = 2;
+
+	self->use = trigger_counter_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_always
+
+==============================================================================
+*/
+
+/*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 (edict_t *ent)
+{
+	// we must have some delay to make sure our use targets are present
+	if (ent->delay < 0.2)
+		ent->delay = 0.2;
+	G_UseTargets(ent, ent);
+}
+
+
+/*
+==============================================================================
+
+trigger_push
+
+==============================================================================
+*/
+
+// PGM
+#define PUSH_ONCE		0x01
+#define PUSH_START_OFF	0x02
+#define PUSH_SILENT		0x04
+// PGM
+
+static int windsound;
+
+void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (strcmp(other->classname, "grenade") == 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+	}
+	else if (other->health > 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+
+		if (other->client)
+		{
+			// don't take falling damage immediately from this
+			VectorCopy (other->velocity, other->client->oldvelocity);
+			if (!(self->spawnflags & PUSH_SILENT) && (other->fly_sound_debounce_time < level.time))
+			{
+				other->fly_sound_debounce_time = level.time + 1.5;
+				gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+			}
+		}
+	}
+	if (self->spawnflags & PUSH_ONCE)
+		G_FreeEdict (self);
+}
+
+//======
+//PGM
+void trigger_push_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+	gi.linkentity (self);
+}
+//PGM
+//======
+
+/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT
+Pushes the player
+"speed"		defaults to 1000
+                                     
+If targeted, it will toggle on and off when used.
+
+START_OFF - toggled trigger_push begins in off setting
+SILENT - doesn't make wind noise
+*/
+void SP_trigger_push (edict_t *self)
+{
+	InitTrigger (self);
+	windsound = gi.soundindex ("misc/windfly.wav");
+	self->touch = trigger_push_touch;
+	if (!self->speed)
+		self->speed = 1000;
+
+//PGM
+	if(self->targetname)		// toggleable
+	{
+		self->use = trigger_push_use;
+		if(self->spawnflags & PUSH_START_OFF)
+			self->solid = SOLID_NOT;
+	}
+	else if(self->spawnflags & PUSH_START_OFF)
+	{
+		gi.dprintf ("trigger_push is START_OFF but not targeted.\n");
+		self->svflags = 0;
+		self->touch = NULL;
+		self->solid = SOLID_BSP;
+		self->movetype = MOVETYPE_PUSH;
+	}
+//PGM
+
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_hurt
+
+==============================================================================
+*/
+
+/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
+Any entity that touches this will be hurt.
+
+It does dmg points of damage each server frame
+
+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 (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+
+void hurt_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	int		dflags;
+
+	if (!other->takedamage)
+		return;
+
+	if (self->timestamp > level.time)
+		return;
+
+	if (self->spawnflags & 16)
+		self->timestamp = level.time + 1;
+	else
+		self->timestamp = level.time + FRAMETIME;
+
+	if (!(self->spawnflags & 4))
+	{
+		if ((level.framenum % 10) == 0)
+			gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
+	}
+
+	if (self->spawnflags & 8)
+		dflags = DAMAGE_NO_PROTECTION;
+	else
+		dflags = 0;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
+}
+
+void SP_trigger_hurt (edict_t *self)
+{
+	InitTrigger (self);
+
+	self->noise_index = gi.soundindex ("world/electro.wav");
+	self->touch = hurt_touch;
+
+	if (!self->dmg)
+		self->dmg = 5;
+
+	if (self->spawnflags & 1)
+		self->solid = SOLID_NOT;
+	else
+		self->solid = SOLID_TRIGGER;
+
+	if (self->spawnflags & 2)
+		self->use = hurt_use;
+
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_gravity
+
+==============================================================================
+*/
+
+//PGM
+void trigger_gravity_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+	gi.linkentity (self);
+}
+//PGM
+
+void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	other->gravity = self->gravity;
+}
+
+/*QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF
+Changes the touching entites gravity to
+the value of "gravity".  1.0 is standard
+gravity for the level.
+
+TOGGLE - trigger_gravity can be turned on and off
+START_OFF - trigger_gravity starts turned off (implies TOGGLE)
+*/
+void SP_trigger_gravity (edict_t *self)
+{
+	if (st.gravity == 0)
+	{
+		gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
+		G_FreeEdict  (self);
+		return;
+	}
+
+	InitTrigger (self);
+
+//PGM
+//	self->gravity = atoi(st.gravity);
+	self->gravity = atof(st.gravity);
+
+	if(self->spawnflags & 1)				// TOGGLE
+		self->use = trigger_gravity_use;
+
+	if(self->spawnflags & 2)				// START_OFF
+	{
+		self->use = trigger_gravity_use;
+		self->solid = SOLID_NOT;
+	}
+
+	self->touch = trigger_gravity_touch;
+//PGM
+
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_monsterjump
+
+==============================================================================
+*/
+
+/*QUAKED trigger_monsterjump (.5 .5 .5) ?
+Walking monsters that touch this will jump in the direction of the trigger's angle
+"speed" default to 200, the speed thrown forward
+"height" default to 200, the speed thrown upwards
+*/
+
+void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->flags & (FL_FLY | FL_SWIM) )
+		return;
+	if (other->svflags & SVF_DEADMONSTER)
+		return;
+	if ( !(other->svflags & SVF_MONSTER))
+		return;
+
+// set XY even if not on ground, so the jump will clear lips
+	other->velocity[0] = self->movedir[0] * self->speed;
+	other->velocity[1] = self->movedir[1] * self->speed;
+	
+	if (!other->groundentity)
+		return;
+	
+	other->groundentity = NULL;
+	other->velocity[2] = self->movedir[2];
+}
+
+void SP_trigger_monsterjump (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 200;
+	if (!st.height)
+		st.height = 200;
+	if (self->s.angles[YAW] == 0)
+		self->s.angles[YAW] = 360;
+	InitTrigger (self);
+	self->touch = trigger_monsterjump_touch;
+	self->movedir[2] = st.height;
+}
+
--- /dev/null
+++ b/rogue/g_turret.c
@@ -1,0 +1,605 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void SpawnTargetingSystem (edict_t *turret);	// PGM
+
+void AnglesNormalize(vec3_t vec)
+{
+	while(vec[0] > 360)
+		vec[0] -= 360;
+	while(vec[0] < 0)
+		vec[0] += 360;
+	while(vec[1] > 360)
+		vec[1] -= 360;
+	while(vec[1] < 0)
+		vec[1] += 360;
+}
+
+float SnapToEights(float x)
+{
+	x *= 8.0;
+	if (x > 0.0)
+		x += 0.5;
+	else
+		x -= 0.5;
+	return 0.125 * (int)x;
+}
+
+
+void turret_blocked(edict_t *self, edict_t *other)
+{
+	edict_t	*attacker;
+
+	if (other->takedamage)
+	{
+		if (self->teammaster->owner)
+			attacker = self->teammaster->owner;
+		else
+			attacker = self->teammaster;
+		T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
+	}
+}
+
+/*QUAKED turret_breach (0 0 0) ?
+This portion of the turret can change both pitch and yaw.
+The model  should be made with a flat pitch.
+It (and the associated base) need to be oriented towards 0.
+Use "angle" to set the starting angle.
+
+"speed"		default 50
+"dmg"		default 10
+"angle"		point this forward
+"target"	point this at an info_notnull at the muzzle tip
+"minpitch"	min acceptable pitch angle : default -30
+"maxpitch"	max acceptable pitch angle : default 30
+"minyaw"	min acceptable yaw angle   : default 0
+"maxyaw"	max acceptable yaw angle   : default 360
+*/
+
+void turret_breach_fire (edict_t *self)
+{
+	vec3_t	f, r, u;
+	vec3_t	start;
+	int		damage;
+	int		speed;
+
+	AngleVectors (self->s.angles, f, r, u);
+	VectorMA (self->s.origin, self->move_origin[0], f, start);
+	VectorMA (start, self->move_origin[1], r, start);
+	VectorMA (start, self->move_origin[2], u, start);
+
+	damage = 100 + qrandom() * 50;
+	speed = 550 + 50 * skill->value;
+	fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
+	gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
+}
+
+void turret_breach_think (edict_t *self)
+{
+	edict_t	*ent;
+	vec3_t	current_angles;
+	vec3_t	delta;
+
+	VectorCopy (self->s.angles, current_angles);
+	AnglesNormalize(current_angles);
+
+	AnglesNormalize(self->move_angles);
+	if (self->move_angles[PITCH] > 180)
+		self->move_angles[PITCH] -= 360;
+
+	// clamp angles to mins & maxs
+	if (self->move_angles[PITCH] > self->pos1[PITCH])
+		self->move_angles[PITCH] = self->pos1[PITCH];
+	else if (self->move_angles[PITCH] < self->pos2[PITCH])
+		self->move_angles[PITCH] = self->pos2[PITCH];
+
+	if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
+	{
+		float	dmin, dmax;
+
+		dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
+		if (dmin < -180)
+			dmin += 360;
+		else if (dmin > 180)
+			dmin -= 360;
+		dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
+		if (dmax < -180)
+			dmax += 360;
+		else if (dmax > 180)
+			dmax -= 360;
+		if (fabs(dmin) < fabs(dmax))
+			self->move_angles[YAW] = self->pos1[YAW];
+		else
+			self->move_angles[YAW] = self->pos2[YAW];
+	}
+
+	VectorSubtract (self->move_angles, current_angles, delta);
+	if (delta[0] < -180)
+		delta[0] += 360;
+	else if (delta[0] > 180)
+		delta[0] -= 360;
+	if (delta[1] < -180)
+		delta[1] += 360;
+	else if (delta[1] > 180)
+		delta[1] -= 360;
+	delta[2] = 0;
+
+	if (delta[0] > self->speed * FRAMETIME)
+		delta[0] = self->speed * FRAMETIME;
+	if (delta[0] < -1 * self->speed * FRAMETIME)
+		delta[0] = -1 * self->speed * FRAMETIME;
+	if (delta[1] > self->speed * FRAMETIME)
+		delta[1] = self->speed * FRAMETIME;
+	if (delta[1] < -1 * self->speed * FRAMETIME)
+		delta[1] = -1 * self->speed * FRAMETIME;
+
+	VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
+
+	self->nextthink = level.time + FRAMETIME;
+
+	for (ent = self->teammaster; ent; ent = ent->teamchain)
+		ent->avelocity[1] = self->avelocity[1];
+
+	// if we have adriver, adjust his velocities
+	if (self->owner)
+	{
+		float	angle;
+		float	target_z;
+		float	diff;
+		vec3_t	target;
+		vec3_t	dir;
+
+		// angular is easy, just copy ours
+		self->owner->avelocity[0] = self->avelocity[0];
+		self->owner->avelocity[1] = self->avelocity[1];
+
+		// x & y
+		angle = self->s.angles[1] + self->owner->move_origin[1];
+		angle *= (M_PI*2 / 360);
+		target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
+		target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
+		target[2] = self->owner->s.origin[2];
+
+		VectorSubtract (target, self->owner->s.origin, dir);
+		self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
+		self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
+
+		// z
+		angle = self->s.angles[PITCH] * (M_PI*2 / 360);
+		target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
+
+		diff = target_z - self->owner->s.origin[2];
+		self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
+
+		if (self->spawnflags & 65536)
+		{
+			turret_breach_fire (self);
+			self->spawnflags &= ~65536;
+		}
+	}
+}
+
+void turret_breach_finish_init (edict_t *self)
+{
+	// get and save info for muzzle location
+	if (!self->target)
+	{
+		gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
+	}
+	else
+	{
+		self->target_ent = G_PickTarget (self->target);
+		if(self->target_ent)
+		{
+			VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
+			G_FreeEdict(self->target_ent);
+		}
+		else
+			gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin));
+	}
+
+	self->teammaster->dmg = self->dmg;
+	self->think = turret_breach_think;
+	self->think (self);
+}
+
+void SP_turret_breach (edict_t *self)
+{
+	self->solid = SOLID_BSP;
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+
+	if (!self->speed)
+		self->speed = 50;
+	if (!self->dmg)
+		self->dmg = 10;
+
+	if (!st.minpitch)
+		st.minpitch = -30;
+	if (!st.maxpitch)
+		st.maxpitch = 30;
+	if (!st.maxyaw)
+		st.maxyaw = 360;
+
+	self->pos1[PITCH] = -1 * st.minpitch;
+	self->pos1[YAW]   = st.minyaw;
+	self->pos2[PITCH] = -1 * st.maxpitch;
+	self->pos2[YAW]   = st.maxyaw;
+
+	self->ideal_yaw = self->s.angles[YAW];
+	self->move_angles[YAW] = self->ideal_yaw;
+
+	self->blocked = turret_blocked;
+
+	self->think = turret_breach_finish_init;
+	self->nextthink = level.time + FRAMETIME;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED turret_base (0 0 0) ?
+This portion of the turret changes yaw only.
+MUST be teamed with a turret_breach.
+*/
+
+void SP_turret_base (edict_t *self)
+{
+	self->solid = SOLID_BSP;
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+	self->blocked = turret_blocked;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
+Must NOT be on the team with the rest of the turret parts.
+Instead it must target the turret_breach.
+*/
+
+void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t);
+void infantry_stand (edict_t *self);
+void monster_use (edict_t *self, edict_t *other, edict_t *activator);
+
+void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	edict_t	*ent;
+
+	// level the gun
+	self->target_ent->move_angles[0] = 0;
+
+	// remove the driver from the end of them team chain
+	for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
+		;
+	ent->teamchain = NULL;
+	self->teammaster = NULL;
+	self->flags &= ~FL_TEAMSLAVE;
+
+	self->target_ent->owner = NULL;
+	self->target_ent->teammaster->owner = NULL;
+
+	infantry_die (self, inflictor, attacker, damage, 0.0);
+}
+
+qboolean FindTarget (edict_t *self);
+
+void turret_driver_think (edict_t *self)
+{
+	vec3_t	target;
+	vec3_t	dir;
+	float	reaction_time;
+
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
+		self->enemy = NULL;
+
+	if (!self->enemy)
+	{
+		if (!FindTarget (self))
+			return;
+		self->monsterinfo.trail_time = level.time;
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+	}
+	else
+	{
+		if (visible (self, self->enemy))
+		{
+			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+			{
+				self->monsterinfo.trail_time = level.time;
+				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+			}
+		}
+		else
+		{
+			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+			return;
+		}
+	}
+
+	// let the turret know where we want it to aim
+	VectorCopy (self->enemy->s.origin, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, self->target_ent->s.origin, dir);
+	vectoangles (dir, self->target_ent->move_angles);
+
+	// decide if we should shoot
+	if (level.time < self->monsterinfo.attack_finished)
+		return;
+
+	reaction_time = (3 - skill->value) * 1.0;
+	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+		return;
+
+	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+	//FIXME how do we really want to pass this along?
+	self->target_ent->spawnflags |= 65536;
+}
+
+void turret_driver_link (edict_t *self)
+{
+	vec3_t	vec;
+	edict_t	*ent;
+
+	self->think = turret_driver_think;
+	self->nextthink = level.time + FRAMETIME;
+
+	self->target_ent = G_PickTarget (self->target);
+	self->target_ent->owner = self;
+	self->target_ent->teammaster->owner = self;
+	VectorCopy (self->target_ent->s.angles, self->s.angles);
+
+	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+	vec[2] = 0;
+	self->move_origin[0] = VectorLength(vec);
+
+	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
+	vectoangles (vec, vec);
+	AnglesNormalize(vec);
+	self->move_origin[1] = vec[1];
+
+	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+	// add the driver to the end of them team chain
+	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
+		;
+	ent->teamchain = self;
+	self->teammaster = self->target_ent->teammaster;
+	self->flags |= FL_TEAMSLAVE;
+}
+
+void SP_turret_driver (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = 0;
+	self->mass = 200;
+	self->viewheight = 24;
+
+	self->die = turret_driver_die;
+	self->monsterinfo.stand = infantry_stand;
+
+	self->flags |= FL_NO_KNOCKBACK;
+
+	level.total_monsters++;
+
+	self->svflags |= SVF_MONSTER;
+	self->s.renderfx |= RF_FRAMELERP;
+	self->takedamage = DAMAGE_AIM;
+	self->use = monster_use;
+	self->clipmask = MASK_MONSTERSOLID;
+	VectorCopy (self->s.origin, self->s.old_origin);
+	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
+
+	if (st.item)
+	{
+		self->item = FindItemByClassname (st.item);
+		if (!self->item)
+			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+	}
+
+	self->think = turret_driver_link;
+	self->nextthink = level.time + FRAMETIME;
+
+	gi.linkentity (self);
+}
+
+//============
+// ROGUE
+
+// invisible turret drivers so we can have unmanned turrets.
+// originally designed to shoot at func_trains and such, so they
+// fire at the center of the bounding box, rather than the entity's
+// origin.
+
+void turret_brain_think (edict_t *self)
+{
+	vec3_t	target;
+	vec3_t	dir;
+	vec3_t	endpos;
+	float	reaction_time;
+	trace_t	trace;
+
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->enemy)
+	{
+		if(!self->enemy->inuse)
+			self->enemy = NULL;
+		else if(self->enemy->takedamage && self->enemy->health <= 0)
+			self->enemy = NULL;
+	}
+
+	if (!self->enemy)
+	{
+		if (!FindTarget (self))
+			return;
+		self->monsterinfo.trail_time = level.time;
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+	}
+	else
+	{
+		VectorAdd (self->enemy->absmax, self->enemy->absmin, endpos);
+		VectorScale (endpos, 0.5, endpos);
+
+		trace = gi.trace (self->target_ent->s.origin, vec3_origin, vec3_origin, endpos, self->target_ent, MASK_SHOT);
+		if(trace.fraction == 1 || trace.ent == self->enemy)
+		{
+			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+			{
+				self->monsterinfo.trail_time = level.time;
+				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+			}
+		}
+		else
+		{
+			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+			return;
+		}
+	}
+
+	// let the turret know where we want it to aim
+	VectorCopy (endpos, target);
+	VectorSubtract (target, self->target_ent->s.origin, dir);
+	vectoangles (dir, self->target_ent->move_angles);
+
+	// decide if we should shoot
+	if (level.time < self->monsterinfo.attack_finished)
+		return;
+
+	if(self->delay)
+		reaction_time = self->delay;
+	else
+		reaction_time = (3 - skill->value) * 1.0;
+	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+		return;
+
+	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+	//FIXME how do we really want to pass this along?
+	self->target_ent->spawnflags |= 65536;
+}
+
+// =================
+// =================
+void turret_brain_link (edict_t *self)
+{
+	vec3_t	vec;
+	edict_t	*ent;
+
+	if (self->killtarget)
+	{
+		self->enemy = G_PickTarget (self->killtarget);
+	}
+
+	self->think = turret_brain_think;
+	self->nextthink = level.time + FRAMETIME;
+
+	self->target_ent = G_PickTarget (self->target);
+	self->target_ent->owner = self;
+	self->target_ent->teammaster->owner = self;
+	VectorCopy (self->target_ent->s.angles, self->s.angles);
+
+	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+	vec[2] = 0;
+	self->move_origin[0] = VectorLength(vec);
+
+	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
+	vectoangles (vec, vec);
+	AnglesNormalize(vec);
+	self->move_origin[1] = vec[1];
+
+	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+	// add the driver to the end of them team chain
+	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
+		;
+	ent->teamchain = self;
+	self->teammaster = self->target_ent->teammaster;
+	self->flags |= FL_TEAMSLAVE;
+}
+
+// =================
+// =================
+void turret_brain_deactivate (edict_t *self, edict_t *, edict_t *)
+{
+	self->think = NULL;
+	self->nextthink = 0;
+}
+
+// =================
+// =================
+void turret_brain_activate (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (!self->enemy)
+	{
+		self->enemy = activator;
+	}
+
+	// wait at least 3 seconds to fire.
+	self->monsterinfo.attack_finished = level.time + 3;
+	self->use = turret_brain_deactivate;
+
+	self->think = turret_brain_link;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
+Invisible brain to drive the turret.
+
+Does not search for targets. If targeted, can only be turned on once
+and then off once. After that they are completely disabled.
+
+"delay" the delay between firing (default ramps for skill level)
+"Target" the turret breach
+"Killtarget" the item you want it to attack.
+Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
+before firing to acquire the target.
+*/
+void SP_turret_invisible_brain (edict_t *self)
+{
+	if (!self->killtarget)
+	{
+		gi.dprintf("turret_invisible_brain with no killtarget!\n");
+		G_FreeEdict (self);
+		return;
+	}
+	if (!self->target)
+	{
+		gi.dprintf("turret_invisible_brain with no target!\n");
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->targetname)
+	{
+		self->use = turret_brain_activate;
+	}
+	else
+	{
+		self->think = turret_brain_link;
+		self->nextthink = level.time + FRAMETIME;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+	gi.linkentity (self);
+}
+
+// ROGUE
+//============
--- /dev/null
+++ b/rogue/g_utils.c
@@ -1,0 +1,677 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
+	result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
+	result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
+}
+
+void G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result)
+{
+	result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + up[0] * distance[2];
+	result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + up[1] * distance[2];
+	result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + up[2] * distance[2];
+}
+
+/*
+=============
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+edict_t *G_Find (edict_t *from, int fieldofs, char *match)
+{
+	char	*s;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+
+	for ( ; from < &g_edicts[globals.num_edicts] ; from++)
+	{
+		if (!from->inuse)
+			continue;
+		s = *(char **) ((byte *)from + fieldofs);
+		if (!s)
+			continue;
+		if (!cistrcmp (s, match))
+			return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=================
+findradius
+
+Returns entities that have origins within a spherical area
+
+findradius (origin, radius)
+=================
+*/
+edict_t *findradius (edict_t *from, vec3_t org, float rad)
+{
+	vec3_t	eorg;
+	int		j;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (!from->inuse)
+			continue;
+		if (from->solid == SOLID_NOT)
+			continue;
+		for (j=0 ; j<3 ; j++)
+			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
+		if (VectorLength(eorg) > rad)
+			continue;
+		return from;
+	}
+
+	return NULL;
+}
+
+/*
+=================
+findradius2
+
+Returns entities that have origins within a spherical area
+
+ROGUE - tweaks for performance for tesla specific code
+only returns entities that can be damaged
+only returns entities that are SVF_DAMAGEABLE
+
+findradius2 (origin, radius)
+=================
+*/
+edict_t *findradius2 (edict_t *from, vec3_t org, float rad)
+{
+	// rad must be positive
+	vec3_t	eorg;
+	int		j;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (!from->inuse)
+			continue;
+		if (from->solid == SOLID_NOT)
+			continue;
+		if (!from->takedamage)
+			continue;
+		if (!(from->svflags & SVF_DAMAGEABLE))
+			continue;
+		for (j=0 ; j<3 ; j++)
+			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
+		if (VectorLength(eorg) > rad)
+			continue;
+		return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=============
+G_PickTarget
+
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+#define MAXCHOICES	8
+
+edict_t *G_PickTarget (char *targetname)
+{
+	edict_t	*ent = NULL;
+	int		num_choices = 0;
+	edict_t	*choice[MAXCHOICES];
+
+	if (!targetname)
+	{
+		gi.dprintf("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)
+	{
+		gi.dprintf("G_PickTarget: target %s not found\n", targetname);
+		return NULL;
+	}
+
+	return choice[rand() % num_choices];
+}
+
+
+
+void Think_Delay (edict_t *ent)
+{
+	G_UseTargets (ent, ent->activator);
+	G_FreeEdict (ent);
+}
+
+/*
+==============================
+G_UseTargets
+
+the global "activator" should be set to the entity that initiated the firing.
+
+If self.delay is set, a DelayedUse entity will be created that will actually
+do the SUB_UseTargets after that many seconds have passed.
+
+Centerprints any self.message to the activator.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void G_UseTargets (edict_t *ent, edict_t *activator)
+{
+	edict_t		*t;
+	edict_t		*master;
+	qboolean	done = false;
+
+//
+// check for a delay
+//
+	if (ent->delay)
+	{
+	// create a temp object to fire at a later time
+		t = G_Spawn();
+		t->classname = "DelayedUse";
+		t->nextthink = level.time + ent->delay;
+		t->think = Think_Delay;
+		t->activator = activator;
+		if (!activator)
+			gi.dprintf ("Think_Delay with no activator\n");
+		t->message = ent->message;
+		t->target = ent->target;
+		t->killtarget = ent->killtarget;
+		return;
+	}
+	
+	
+//
+// print the message
+//
+	if ((ent->message) && !(activator->svflags & SVF_MONSTER))
+	{
+		gi.centerprintf (activator, "%s", ent->message);
+		if (ent->noise_index)
+			gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
+		else
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+
+//
+// kill killtargets
+//
+	if (ent->killtarget)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
+		{
+			// PMM - if this entity is part of a train, cleanly remove it
+			if (t->flags & FL_TEAMSLAVE)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("Removing %s from train!\n", t->classname);
+
+				if (t->teammaster)
+				{
+					master = t->teammaster;
+					while (!done)
+					{
+						if (master->teamchain == t)
+						{
+							master->teamchain = t->teamchain;
+							done = true;
+						}
+						master = master->teamchain;
+						if (!master)
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//								gi.dprintf ("Couldn't find myself in master's chain, ignoring!\n");
+						}
+					}
+				}
+				else
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("No master to free myself from, ignoring!\n");
+				}
+			}
+			// PMM
+			G_FreeEdict (t);
+			if (!ent->inuse)
+			{
+				gi.dprintf("entity was removed while using killtargets\n");
+				return;
+			}
+		}
+	}
+
+//
+// fire targets
+//
+	if (ent->target)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->target)))
+		{
+			// doors fire area portals in a specific way
+			if (!cistrcmp(t->classname, "func_areaportal") &&
+				(!cistrcmp(ent->classname, "func_door") || !cistrcmp(ent->classname, "func_door_rotating")))
+				continue;
+
+			if (t == ent)
+			{
+				gi.dprintf ("WARNING: Entity used itself.\n");
+			}
+			else
+			{
+				if (t->use)
+					t->use (t, ent, activator);
+			}
+			if (!ent->inuse)
+			{
+				gi.dprintf("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 (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;
+}
+
+
+vec3_t VEC_UP		= {0, -1, 0};
+vec3_t MOVEDIR_UP	= {0, 0, 1};
+vec3_t VEC_DOWN		= {0, -2, 0};
+vec3_t MOVEDIR_DOWN	= {0, 0, -1};
+
+void G_SetMovedir (vec3_t angles, vec3_t movedir)
+{
+	if (VectorCompare (angles, VEC_UP))
+	{
+		VectorCopy (MOVEDIR_UP, movedir);
+	}
+	else if (VectorCompare (angles, VEC_DOWN))
+	{
+		VectorCopy (MOVEDIR_DOWN, movedir);
+	}
+	else
+	{
+		AngleVectors (angles, movedir, NULL, NULL);
+	}
+
+	VectorClear (angles);
+}
+
+
+float vectoyaw (vec3_t vec)
+{
+	float	yaw;
+	
+	// PMM - fixed to correct for pitch of 0
+	if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
+		if (vec[YAW] == 0)
+			yaw = 0;
+		else if (vec[YAW] > 0)
+			yaw = 90;
+		else
+			yaw = 270;
+	else
+	{
+		yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+		if (yaw < 0)
+			yaw += 360;
+	}
+
+	return yaw;
+}
+
+float vectoyaw2 (vec3_t vec)
+{
+	float	yaw;
+	
+	// PMM - fixed to correct for pitch of 0
+	if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
+		if (vec[YAW] == 0)
+			yaw = 0;
+		else if (vec[YAW] > 0)
+			yaw = 90;
+		else
+			yaw = 270;
+	else
+	{
+		yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+		if (yaw < 0)
+			yaw += 360;
+	}
+
+	return yaw;
+}
+
+
+void vectoangles (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
+	{
+	// PMM - fixed to correct for pitch of 0
+		if (value1[0])
+			yaw = (int) (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 = (int) (atan2(value1[2], forward) * 180 / M_PI);
+		if (pitch < 0)
+			pitch += 360;
+	}
+
+	angles[PITCH] = -pitch;
+	angles[YAW] = yaw;
+	angles[ROLL] = 0;
+}
+
+char *G_CopyString (char *in)
+{
+	char	*out;
+	
+	out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
+	strcpy (out, in);
+	return out;
+}
+
+
+void G_InitEdict (edict_t *e)
+{
+	// ROGUE
+	// FIXME -
+	//   this fixes a bug somewhere that is settling "nextthink" for an entity that has
+	//   already been released.  nextthink is being set to FRAMETIME after level.time, 
+	//   since freetime = nextthink - 0.1
+	if (e->nextthink)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("G_SPAWN:  Fixed bad nextthink time\n");
+		e->nextthink = 0;
+	}
+	// ROGUE
+
+	e->inuse = true;
+	e->classname = "noclass";
+	e->gravity = 1.0;
+	e->s.number = e - g_edicts;
+
+//PGM - do this before calling the spawn function so it can be overridden.
+#ifdef ROGUE_GRAVITY
+	e->gravityVector[0] =  0.0;
+	e->gravityVector[1] =  0.0;
+	e->gravityVector[2] = -1.0;
+#endif
+//PGM
+}
+
+/*
+=================
+G_Spawn
+
+Either finds a free edict, or allocates a new one.
+Try to avoid reusing an entity that was recently freed, because it
+can cause the client to think the entity morphed into something else
+instead of being removed and recreated, which can cause interpolated
+angles and bad trails.
+=================
+*/
+edict_t *G_Spawn (void)
+{
+	int			i;
+	edict_t		*e;
+
+	e = &g_edicts[(int)maxclients->value+1];
+	for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
+	{
+		// the first couple seconds of server time can involve a lot of
+		// freeing and allocating, so relax the replacement policy
+		if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
+		{
+			G_InitEdict (e);
+			return e;
+		}
+	}
+	
+	if (i == game.maxentities)
+		gi.error ("ED_Alloc: no free edicts");
+		
+	globals.num_edicts++;
+	G_InitEdict (e);
+	return e;
+}
+
+/*
+=================
+G_FreeEdict
+
+Marks the edict as free
+=================
+*/
+void G_FreeEdict (edict_t *ed)
+{
+	gi.unlinkentity (ed);		// unlink from world
+
+	if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
+	{
+//		gi.dprintf("tried to free special edict\n");
+		return;
+	}
+
+	memset (ed, 0, sizeof(*ed));
+	ed->classname = "freed";
+	ed->freetime = level.time;
+	ed->inuse = false;
+}
+
+
+/*
+============
+G_TouchTriggers
+
+============
+*/
+void	G_TouchTriggers (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	// dead things don't activate triggers!
+	if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
+		return;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_TRIGGERS);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (!hit->touch)
+			continue;
+		hit->touch (hit, ent, NULL, NULL);
+	}
+}
+
+/*
+============
+G_TouchSolids
+
+Call after linking a new trigger in during gameplay
+to force all entities it covers to immediately touch it
+============
+*/
+void	G_TouchSolids (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_SOLID);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (ent->touch)
+			ent->touch (hit, ent, NULL, NULL);
+		if (!ent->inuse)
+			break;
+	}
+}
+
+
+
+
+/*
+==============================================================================
+
+Kill box
+
+==============================================================================
+*/
+
+/*
+=================
+KillBox
+
+Kills all entities that would touch the proposed new positioning
+of ent.  Ent should be unlinked before calling this!
+=================
+*/
+qboolean KillBox (edict_t *ent)
+{
+	trace_t		tr;
+
+	while (1)
+	{
+		tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
+		if (!tr.ent)
+			break;
+
+		// nail it
+		T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+
+		// if we didn't kill it, fail
+		if (tr.ent->solid)
+			return false;
+	}
+
+	return true;		// all clear
+}
--- /dev/null
+++ b/rogue/g_weapon.c
@@ -1,0 +1,913 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+/*
+=================
+check_dodge
+
+This is a support routine used when a client is firing
+a non-instant attack weapon.  It checks to see if a
+monster's dodge function should be called.
+=================
+*/
+//static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
+void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)		//PGM
+{
+	vec3_t	end;
+	vec3_t	v;
+	trace_t	tr;
+	float	eta;
+
+	// easy mode only ducks one quarter the time
+	if (skill->value == 0)
+	{
+		if (qrandom() > 0.25)
+			return;
+	}
+	VectorMA (start, 8192, dir, end);
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
+	{
+			VectorSubtract (tr.endpos, start, v);
+			eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
+//			tr.ent->monsterinfo.dodge (tr.ent, self, eta);
+			tr.ent->monsterinfo.dodge (tr.ent, self, eta, &tr);
+	}
+}
+
+
+/*
+=================
+fire_hit
+
+Used for all impact (hit/punch/slash) attacks
+=================
+*/
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
+{
+	trace_t		tr;
+	vec3_t		forward, right, up;
+	vec3_t		v;
+	vec3_t		point;
+	float		range;
+	vec3_t		dir;
+
+	//see if enemy is in range
+	VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+	range = VectorLength(dir);
+	if (range > aim[0])
+		return false;
+
+	if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
+	{
+		// the hit is straight on so back the range up to the edge of their bbox
+		range -= self->enemy->maxs[0];
+	}
+	else
+	{
+		// this is a side hit so adjust the "right" value out to the edge of their bbox
+		if (aim[1] < 0)
+			aim[1] = self->enemy->mins[0];
+		else
+			aim[1] = self->enemy->maxs[0];
+	}
+
+	VectorMA (self->s.origin, range, dir, point);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
+	if (tr.fraction < 1)
+	{
+		if (!tr.ent->takedamage)
+			return false;
+		// if it will hit any client/monster then hit the one we wanted to hit
+		if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+			tr.ent = self->enemy;
+	}
+
+	AngleVectors(self->s.angles, forward, right, up);
+	VectorMA (self->s.origin, range, forward, point);
+	VectorMA (point, aim[1], right, point);
+	VectorMA (point, aim[2], up, point);
+	VectorSubtract (point, self->enemy->s.origin, dir);
+
+	// do the damage
+	T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
+
+	if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		return false;
+
+	// do our special form of knockback here
+	VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
+	VectorSubtract (v, point, v);
+	VectorNormalize (v);
+	VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
+	if (self->enemy->velocity[2] > 0)
+		self->enemy->groundentity = NULL;
+	return true;
+}
+
+
+/*
+=================
+fire_lead
+
+This is an internal support routine used for bullet/pellet based weapons.
+=================
+*/
+static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
+{
+	trace_t		tr;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	float		r;
+	float		u;
+	vec3_t		water_start;
+	qboolean	water = false;
+	int			content_mask = MASK_SHOT | MASK_WATER;
+
+	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+	if (!(tr.fraction < 1.0))
+	{
+		vectoangles (aimdir, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*hspread;
+		u = crandom()*vspread;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		if (gi.pointcontents (start) & MASK_WATER)
+		{
+			water = true;
+			VectorCopy (start, water_start);
+			content_mask &= ~MASK_WATER;
+		}
+
+		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
+
+		// see if we hit water
+		if (tr.contents & MASK_WATER)
+		{
+			int		color;
+
+			water = true;
+			VectorCopy (tr.endpos, water_start);
+
+			if (!VectorCompare (start, tr.endpos))
+			{
+				if (tr.contents & CONTENTS_WATER)
+				{
+					if (strcmp(tr.surface->name, "*brwater") == 0)
+						color = SPLASH_BROWN_WATER;
+					else
+						color = SPLASH_BLUE_WATER;
+				}
+				else if (tr.contents & CONTENTS_SLIME)
+					color = SPLASH_SLIME;
+				else if (tr.contents & CONTENTS_LAVA)
+					color = SPLASH_LAVA;
+				else
+					color = SPLASH_UNKNOWN;
+
+				if (color != SPLASH_UNKNOWN)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (TE_SPLASH);
+					gi.WriteByte (8);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.WriteByte (color);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+				}
+
+				// change bullet's course when it enters water
+				VectorSubtract (end, start, dir);
+				vectoangles (dir, dir);
+				AngleVectors (dir, forward, right, up);
+				r = crandom()*hspread*2;
+				u = crandom()*vspread*2;
+				VectorMA (water_start, 8192, forward, end);
+				VectorMA (end, r, right, end);
+				VectorMA (end, u, up, end);
+			}
+
+			// re-trace ignoring water this time
+			tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
+		}
+	}
+
+	// send gun puff / flash
+	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+	{
+		if (tr.fraction < 1.0)
+		{
+			if (tr.ent->takedamage)
+			{
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
+			}
+			else
+			{
+				if (strncmp (tr.surface->name, "sky", 3) != 0)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (te_impact);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+
+					if (self->client)
+						PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+				}
+			}
+		}
+	}
+
+	// if went through water, determine where the end and make a bubble trail
+	if (water)
+	{
+		vec3_t	pos;
+
+		VectorSubtract (tr.endpos, water_start, dir);
+		VectorNormalize (dir);
+		VectorMA (tr.endpos, -2, dir, pos);
+		if (gi.pointcontents (pos) & MASK_WATER)
+			VectorCopy (pos, tr.endpos);
+		else
+			tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+
+		VectorAdd (water_start, tr.endpos, pos);
+		VectorScale (pos, 0.5, pos);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BUBBLETRAIL);
+		gi.WritePosition (water_start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (pos, MULTICAST_PVS);
+	}
+}
+
+
+/*
+=================
+fire_bullet
+
+Fires a single round.  Used for machinegun and chaingun.  Would be fine for
+pistols, rifles, etc....
+=================
+*/
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
+{
+	fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_shotgun
+
+Shoots shotgun pellets.  Used by shotgun and super shotgun.
+=================
+*/
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
+{
+	int		i;
+
+	for (i = 0; i < count; i++)
+		fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_blaster
+
+Fires a single blaster bolt.  Used by the blaster and hyper blaster.
+=================
+*/
+void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	int		mod;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// PMM - crash prevention
+	if (self->owner && self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		if (self->spawnflags & 1)
+			mod = MOD_HYPERBLASTER;
+		else
+			mod = MOD_BLASTER;
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+	}
+	else
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BLASTER);
+		gi.WritePosition (self->s.origin);
+		if (!plane)
+			gi.WriteDir (vec3_origin);
+		else
+			gi.WriteDir (plane->normal);
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+
+	G_FreeEdict (self);
+}
+
+void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
+{
+	edict_t	*bolt;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn();
+	bolt->svflags = SVF_DEADMONSTER;
+	// yes, I know it looks weird that projectiles are deadmonsters
+	// what this means is that when prediction is used against the object
+	// (blaster/hyperblaster shots), the player won't be solid clipped against
+	// the object.  Right now trying to run into a firing hyperblaster
+	// is very jerky since you are predicted 'against' the shots.
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->s.effects |= effect;
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+	bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
+	bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
+	bolt->owner = self;
+	bolt->touch = blaster_touch;
+	bolt->nextthink = level.time + 2;
+	bolt->think = G_FreeEdict;
+	bolt->dmg = damage;
+	bolt->classname = "bolt";
+	if (hyper)
+		bolt->spawnflags = 1;
+	gi.linkentity (bolt);
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+}	
+
+
+/*
+=================
+fire_grenade
+=================
+*/
+//static void Grenade_Explode (edict_t *ent)
+void Grenade_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+	int			mod;
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	//FIXME: if we are onground then raise our Z just a bit since we are a point?
+	if (ent->enemy)
+	{
+		float	points;
+		vec3_t	v;
+		vec3_t	dir;
+
+		VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
+		VectorMA (ent->enemy->s.origin, 0.5, v, v);
+		VectorSubtract (ent->s.origin, v, v);
+		points = ent->dmg - 0.5 * VectorLength (v);
+		VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
+		if (ent->spawnflags & 1)
+			mod = MOD_HANDGRENADE;
+		else
+			mod = MOD_GRENADE;
+		T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+	}
+
+	if (ent->spawnflags & 2)
+		mod = MOD_HELD_GRENADE;
+	else if (ent->spawnflags & 1)
+		mod = MOD_HG_SPLASH;
+	else
+		mod = MOD_G_SPLASH;
+	T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
+
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION_WATER);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	}
+	else
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION);
+	}
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!other->takedamage)
+	{
+		if (ent->spawnflags & 1)
+		{
+			if (qrandom() > 0.5)
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+			else
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+		}
+		else
+		{
+			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+
+	ent->enemy = other;
+	Grenade_Explode (ent);
+}
+
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "grenade";
+
+	gi.linkentity (grenade);
+}
+
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "hgrenade";
+	if (held)
+		grenade->spawnflags = 3;
+	else
+		grenade->spawnflags = 1;
+	grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
+
+	if (timer <= 0.0)
+		Grenade_Explode (grenade);
+	else
+	{
+		gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
+		gi.linkentity (grenade);
+	}
+}
+
+
+/*
+=================
+fire_rocket
+=================
+*/
+void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	vec3_t		origin;
+	int			n;
+
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	// calculate position for the explosion entity
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+	if (other->takedamage)
+	{
+		T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
+	}
+	else
+	{
+		// don't throw any debris in net games
+		if (!deathmatch->value && !coop->value)
+		{
+			if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
+			{
+				n = rand() % 5;
+				while(n--)
+					ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
+			}
+		}
+	}
+
+	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
+
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+		gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
+{
+	edict_t	*rocket;
+
+	rocket = G_Spawn();
+	VectorCopy (start, rocket->s.origin);
+	VectorCopy (dir, rocket->movedir);
+	vectoangles (dir, rocket->s.angles);
+	VectorScale (dir, speed, rocket->velocity);
+	rocket->movetype = MOVETYPE_FLYMISSILE;
+	rocket->clipmask = MASK_SHOT;
+	rocket->solid = SOLID_BBOX;
+	rocket->s.effects |= EF_ROCKET;
+	VectorClear (rocket->mins);
+	VectorClear (rocket->maxs);
+	rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
+	rocket->owner = self;
+	rocket->touch = rocket_touch;
+	rocket->nextthink = level.time + 8000/speed;
+	rocket->think = G_FreeEdict;
+	rocket->dmg = damage;
+	rocket->radius_dmg = radius_damage;
+	rocket->dmg_radius = damage_radius;
+	rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
+	rocket->classname = "rocket";
+
+	if (self->client)
+		check_dodge (self, rocket->s.origin, dir, speed);
+
+	gi.linkentity (rocket);
+}
+
+
+/*
+=================
+fire_rail
+=================
+*/
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
+{
+	vec3_t		from;
+	vec3_t		end;
+	trace_t		tr;
+	edict_t		*ignore;
+	int			mask;
+	qboolean	water;
+
+	VectorMA (start, 8192, aimdir, end);
+	VectorCopy (start, from);
+	ignore = self;
+	water = false;
+	mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
+	while (ignore)
+	{
+		tr = gi.trace (from, NULL, NULL, end, ignore, mask);
+
+		if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
+			water = true;
+		}
+		else
+		{
+			//ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc)
+			if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
+				(tr.ent->svflags & SVF_DAMAGEABLE) ||
+				(tr.ent->solid == SOLID_BBOX))
+				ignore = tr.ent;
+			else
+				ignore = NULL;
+
+			if ((tr.ent != self) && (tr.ent->takedamage))
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
+		}
+
+		VectorCopy (tr.endpos, from);
+	}
+
+	// send gun puff / flash
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_RAILTRAIL);
+	gi.WritePosition (start);
+	gi.WritePosition (tr.endpos);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+//	gi.multicast (start, MULTICAST_PHS);
+	if (water)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_RAILTRAIL);
+		gi.WritePosition (start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (tr.endpos, MULTICAST_PHS);
+	}
+
+	if (self->client)
+		PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+}
+
+
+/*
+=================
+fire_bfg
+=================
+*/
+void bfg_explode (edict_t *self)
+{
+	edict_t	*ent;
+	float	points;
+	vec3_t	v;
+	float	dist;
+
+	if (self->s.frame == 0)
+	{
+		// the BFG effect
+		ent = NULL;
+		while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
+		{
+			if (!ent->takedamage)
+				continue;
+			if (ent == self->owner)
+				continue;
+			if (!CanDamage (ent, self))
+				continue;
+			if (!CanDamage (ent, self->owner))
+				continue;
+
+			VectorAdd (ent->mins, ent->maxs, v);
+			VectorMA (ent->s.origin, 0.5, v, v);
+			VectorSubtract (self->s.origin, v, v);
+			dist = VectorLength(v);
+			points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
+// PMM - happened to notice this copy/paste bug
+//			if (ent == self->owner)
+//				points = points * 0.5;
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_BFG_EXPLOSION);
+			gi.WritePosition (ent->s.origin);
+			gi.multicast (ent->s.origin, MULTICAST_PHS);
+			T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
+		}
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+	self->s.frame++;
+	if (self->s.frame == 5)
+		self->think = G_FreeEdict;
+}
+
+void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	// core explosion - prevents firing it into the wall/floor
+	if (other->takedamage)
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
+	T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
+	self->solid = SOLID_NOT;
+	self->touch = NULL;
+	VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
+	VectorClear (self->velocity);
+	self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
+	self->s.frame = 0;
+	self->s.sound = 0;
+	self->s.effects &= ~EF_ANIM_ALLFAST;
+	self->think = bfg_explode;
+	self->nextthink = level.time + FRAMETIME;
+	self->enemy = other;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BFG_BIGEXPLOSION);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+
+void bfg_think (edict_t *self)
+{
+	edict_t	*ent;
+	edict_t	*ignore;
+	vec3_t	point;
+	vec3_t	dir;
+	vec3_t	start;
+	vec3_t	end;
+	int		dmg;
+	trace_t	tr;
+
+	if (deathmatch->value)
+		dmg = 5;
+	else
+		dmg = 10;
+
+	ent = NULL;
+	while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
+	{
+		if (ent == self)
+			continue;
+
+		if (ent == self->owner)
+			continue;
+
+		if (!ent->takedamage)
+			continue;
+
+		//ROGUE - make tesla hurt by bfg
+		if (!(ent->svflags & SVF_MONSTER) && !(ent->svflags & SVF_DAMAGEABLE) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
+//		if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)
+//			&& (strcmp(ent->classname, "tesla") != 0))
+			continue;
+
+		VectorMA (ent->absmin, 0.5, ent->size, point);
+
+		VectorSubtract (point, self->s.origin, dir);
+		VectorNormalize (dir);
+
+		ignore = self;
+		VectorCopy (self->s.origin, start);
+		VectorMA (start, 2048, dir, end);
+		while(1)
+		{
+			tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+
+			if (!tr.ent)
+				break;
+
+			// hurt it if we can
+			if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
+				T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
+
+			// if we hit something that's not a monster or player we're done
+//			if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+			if (!(tr.ent->svflags & SVF_MONSTER) && !(tr.ent->svflags & SVF_DAMAGEABLE) && (!tr.ent->client))
+			{
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (4);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+				break;
+			}
+
+			ignore = tr.ent;
+			VectorCopy (tr.endpos, start);
+		}
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BFG_LASER);
+		gi.WritePosition (self->s.origin);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (self->s.origin, MULTICAST_PHS);
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
+{
+	edict_t	*bfg;
+
+	bfg = G_Spawn();
+	VectorCopy (start, bfg->s.origin);
+	VectorCopy (dir, bfg->movedir);
+	vectoangles (dir, bfg->s.angles);
+	VectorScale (dir, speed, bfg->velocity);
+	bfg->movetype = MOVETYPE_FLYMISSILE;
+	bfg->clipmask = MASK_SHOT;
+	bfg->solid = SOLID_BBOX;
+	bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
+	VectorClear (bfg->mins);
+	VectorClear (bfg->maxs);
+	bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
+	bfg->owner = self;
+	bfg->touch = bfg_touch;
+	bfg->nextthink = level.time + 8000/speed;
+	bfg->think = G_FreeEdict;
+	bfg->radius_dmg = damage;
+	bfg->dmg_radius = damage_radius;
+	bfg->classname = "bfg blast";
+	bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
+
+	bfg->think = bfg_think;
+	bfg->nextthink = level.time + FRAMETIME;
+	bfg->teammaster = bfg;
+	bfg->teamchain = NULL;
+
+	if (self->client)
+		check_dodge (self, bfg->s.origin, dir, speed);
+
+	gi.linkentity (bfg);
+}
--- /dev/null
+++ b/rogue/game.h
@@ -1,0 +1,1598 @@
+
+// game.h -- game dll information visible to server
+
+#define	GAME_API_VERSION	3
+
+// edict->svflags
+
+#define	SVF_NOCLIENT			0x00000001	// don't send entity to clients, even if it has effects
+#define	SVF_DEADMONSTER			0x00000002	// treat as CONTENTS_DEADMONSTER for collision
+#define	SVF_MONSTER				0x00000004	// treat as CONTENTS_MONSTER for collision
+//ROGUE -- added for things that are damageable, but not monsters
+// right now, only the tesla has this
+#define SVF_DAMAGEABLE			0x00000008
+//ROGUE end
+
+// edict->solid values
+
+typedef enum
+{
+SOLID_NOT,			// no interaction with other objects
+SOLID_TRIGGER,		// only touch when inside, after moving
+SOLID_BBOX,			// touch on edge
+SOLID_BSP			// bsp clip, touch on edge
+} solid_t;
+
+//===============================================================
+
+// link_t is only used for entity area links now
+typedef struct link_s
+{
+	struct link_s	*prev, *next;
+} link_t;
+
+#define	MAX_ENT_CLUSTERS	16
+
+
+typedef struct edict_t edict_t;
+typedef struct gclient_t gclient_t;
+
+
+//
+// functions provided by the main engine
+//
+typedef struct
+{
+	// special messages
+	void	(*bprintf) (int printlevel, char *fmt, ...);
+	void	(*dprintf) (char *fmt, ...);
+	void	(*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
+	void	(*centerprintf) (edict_t *ent, char *fmt, ...);
+	void	(*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
+	void	(*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
+
+	// config strings hold all the index strings, the lightstyles,
+	// and misc data like the sky definition and cdtrack.
+	// All of the current configstrings are sent to clients when
+	// they connect, and changes are sent to all connected clients.
+	void	(*configstring) (int num, char *string);
+
+	void	(*error) (char *fmt, ...);
+
+	// the *index functions create configstrings and some internal server state
+	int		(*modelindex) (char *name);
+	int		(*soundindex) (char *name);
+	int		(*imageindex) (char *name);
+
+	void	(*setmodel) (edict_t *ent, char *name);
+
+	// collision detection
+	trace_t	(*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
+	int		(*pointcontents) (vec3_t point);
+	qboolean	(*inPVS) (vec3_t p1, vec3_t p2);
+	qboolean	(*inPHS) (vec3_t p1, vec3_t p2);
+	void		(*SetAreaPortalState) (int portalnum, qboolean open);
+	qboolean	(*AreasConnected) (int area1, int area2);
+
+	// 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.
+	void	(*linkentity) (edict_t *ent);
+	void	(*unlinkentity) (edict_t *ent);		// call before removing an interactive edict
+	int		(*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list,	int maxcount, int areatype);
+	void	(*Pmove) (pmove_t *pmove);		// player movement code common with client prediction
+
+	// network messaging
+	void	(*multicast) (vec3_t origin, multicast_t to);
+	void	(*unicast) (edict_t *ent, qboolean reliable);
+	void	(*WriteChar) (int c);
+	void	(*WriteByte) (int c);
+	void	(*WriteShort) (int c);
+	void	(*WriteLong) (int c);
+	void	(*WriteFloat) (float f);
+	void	(*WriteString) (char *s);
+	void	(*WritePosition) (vec3_t pos);	// some fractional bits
+	void	(*WriteDir) (vec3_t pos);		// single byte encoded, very coarse
+	void	(*WriteAngle) (float f);
+
+	// managed memory allocation
+	void	*(*TagMalloc) (int size, int tag);
+	void	(*TagFree) (void *block);
+	void	(*FreeTags) (int tag);
+
+	// console variable interaction
+	cvar_t	*(*cvar) (char *var_name, char *value, int flags);
+	cvar_t	*(*cvar_set) (char *var_name, char *value);
+	cvar_t	*(*cvar_forceset) (char *var_name, char *value);
+
+	// ClientCommand and ServerCommand parameter access
+	int		(*argc) (void);
+	char	*(*argv) (int n);
+	char	*(*args) (void);	// concatenation of all argv >= 1
+
+	// add commands to the server console as if they were typed in
+	// for map changing, etc
+	void	(*AddCommandString) (char *text);
+
+	void	(*DebugGraph) (float value, int color);
+} game_import_t;
+
+//
+// functions exported by the game subsystem
+//
+typedef struct
+{
+	int			apiversion;
+
+	// the init function will only be called when a game starts,
+	// not each time a level is loaded.  Persistant data for clients
+	// and the server can be allocated in init
+	void		(*Init) (void);
+	void		(*Shutdown) (void);
+
+	// each new level entered will cause a call to SpawnEntities
+	void		(*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
+
+	// Read/Write Game is for storing persistant cross level information
+	// about the world state and the clients.
+	// WriteGame is called every time a level is exited.
+	// ReadGame is called on a loadgame.
+	void		(*WriteGame) (char *filename, qboolean autosave);
+	void		(*ReadGame) (char *filename);
+
+	// ReadLevel is called after the default map information has been
+	// loaded with SpawnEntities
+	void		(*WriteLevel) (char *filename);
+	void		(*ReadLevel) (char *filename);
+
+	qboolean	(*ClientConnect) (edict_t *ent, char *userinfo);
+	void		(*ClientBegin) (edict_t *ent);
+	void		(*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
+	void		(*ClientDisconnect) (edict_t *ent);
+	void		(*ClientCommand) (edict_t *ent);
+	void		(*ClientThink) (edict_t *ent, usercmd_t *cmd);
+
+	void		(*RunFrame) (void);
+
+	// ServerCommand will be called when an "sv <command>" command is issued on the
+	// server console.
+	// The game can issue gi.argc() / gi.argv() commands to get the rest
+	// of the parameters
+	void		(*ServerCommand) (void);
+
+	//
+	// global variables shared between game and server
+	//
+
+	// The edict array is allocated in the game dll so it
+	// can vary in size from one game to another.
+	// 
+	// The size will be fixed when ge->Init() is called
+	edict_t	*edicts;
+	int			edict_size;
+	int			num_edicts;		// current number, <= max_edicts
+	int			max_edicts;
+} game_export_t;
+
+game_export_t*	GetGameAPI(game_import_t *);
+
+
+// the "gameversion" client command will print this plus compile date
+#define	GAMEVERSION	"baseq2"
+
+#define qmin(a,b) ((a) < (b) ? (a) : (b))
+#define qmax(a,b) ((a) > (b) ? (a) : (b))
+
+// view pitching times
+#define DAMAGE_TIME		0.5
+#define	FALL_TIME		0.3
+
+// ROGUE- id killed this weapon
+#define	KILL_DISRUPTOR	1
+// rogue
+
+// edict->spawnflags
+// these are set with checkboxes on each entity in the map editor
+#define	SPAWNFLAG_NOT_EASY			0x00000100
+#define	SPAWNFLAG_NOT_MEDIUM		0x00000200
+#define	SPAWNFLAG_NOT_HARD			0x00000400
+#define	SPAWNFLAG_NOT_DEATHMATCH	0x00000800
+#define	SPAWNFLAG_NOT_COOP			0x00001000
+
+// edict->flags
+#define	FL_FLY					0x00000001
+#define	FL_SWIM					0x00000002	// implied immunity to drowining
+#define FL_IMMUNE_LASER			0x00000004
+#define	FL_INWATER				0x00000008
+#define	FL_GODMODE				0x00000010
+#define	FL_NOTARGET				0x00000020
+#define FL_IMMUNE_SLIME			0x00000040
+#define FL_IMMUNE_LAVA			0x00000080
+#define	FL_PARTIALGROUND		0x00000100	// not all corners are valid
+#define	FL_WATERJUMP			0x00000200	// player jumping out of water
+#define	FL_TEAMSLAVE			0x00000400	// not the first on the team
+#define FL_NO_KNOCKBACK			0x00000800
+#define FL_POWER_ARMOR			0x00001000	// power armor (if any) is active
+#define FL_RESPAWN				0x80000000	// used for item respawning
+
+//ROGUE
+#define FL_MECHANICAL			0x00002000	// entity is mechanical, use sparks not blood
+#define FL_SAM_RAIMI			0x00004000	// entity is in sam raimi cam mode
+#define FL_DISGUISED			0x00008000	// entity is in disguise, monsters will not recognize.
+#define	FL_NOGIB				0x00010000	// player has been vaporized by a nuke, drop no gibs
+//ROGUE
+
+#define	FRAMETIME		0.1
+
+// memory tags to allow dynamic memory to be cleaned up
+#define	TAG_GAME	765		// clear when unloading the dll
+#define	TAG_LEVEL	766		// clear when loading a new level
+
+
+#define MELEE_DISTANCE	80
+
+#define BODY_QUEUE_SIZE		8
+
+typedef enum
+{
+	DAMAGE_NO,
+	DAMAGE_YES,			// will take damage if hit
+	DAMAGE_AIM			// auto targeting recognizes this
+} damage_t;
+
+typedef enum 
+{
+	WEAPON_READY, 
+	WEAPON_ACTIVATING,
+	WEAPON_DROPPING,
+	WEAPON_FIRING
+} weaponstate_t;
+
+typedef enum
+{
+	AMMO_BULLETS,
+	AMMO_SHELLS,
+	AMMO_ROCKETS,
+	AMMO_GRENADES,
+	AMMO_CELLS,
+	AMMO_SLUGS,
+
+	//ROGUE
+	AMMO_FLECHETTES,
+	AMMO_TESLA,
+#ifdef KILL_DISRUPTOR
+	AMMO_PROX
+#else
+	AMMO_PROX,
+	AMMO_DISRUPTOR
+#endif
+} ammo_t;
+
+
+//deadflag
+#define DEAD_NO					0
+#define DEAD_DYING				1
+#define DEAD_DEAD				2
+#define DEAD_RESPAWNABLE		3
+
+//range
+#define RANGE_MELEE				0
+#define RANGE_NEAR				1
+#define RANGE_MID				2
+#define RANGE_FAR				3
+
+//gib types
+#define GIB_ORGANIC				0
+#define GIB_METALLIC			1
+
+//monster ai flags
+#define AI_STAND_GROUND			0x00000001
+#define AI_TEMP_STAND_GROUND	0x00000002
+#define AI_SOUND_TARGET			0x00000004
+#define AI_LOST_SIGHT			0x00000008
+#define AI_PURSUIT_LAST_SEEN	0x00000010
+#define AI_PURSUE_NEXT			0x00000020
+#define AI_PURSUE_TEMP			0x00000040
+#define AI_HOLD_FRAME			0x00000080
+#define AI_GOOD_GUY				0x00000100
+#define AI_BRUTAL				0x00000200
+#define AI_NOSTEP				0x00000400
+#define AI_DUCKED				0x00000800
+#define AI_COMBAT_POINT			0x00001000
+#define AI_MEDIC				0x00002000
+#define AI_RESURRECTING			0x00004000
+
+//ROGUE
+#define AI_WALK_WALLS			0x00008000
+#define AI_MANUAL_STEERING		0x00010000
+#define AI_TARGET_ANGER			0x00020000
+#define AI_DODGING				0x00040000
+#define AI_CHARGING				0x00080000
+#define AI_HINT_PATH			0x00100000
+#define	AI_IGNORE_SHOTS			0x00200000
+// PMM - FIXME - last second added for E3 .. there's probably a better way to do this, but
+// this works
+#define	AI_DO_NOT_COUNT			0x00400000	// set for healed monsters
+#define	AI_SPAWNED_CARRIER		0x00800000	// both do_not_count and spawned are set for spawned monsters
+#define	AI_SPAWNED_MEDIC_C		0x01000000	// both do_not_count and spawned are set for spawned monsters
+#define	AI_SPAWNED_WIDOW		0x02000000	// both do_not_count and spawned are set for spawned monsters
+#define AI_SPAWNED_MASK			0x03800000	// mask to catch all three flavors of spawned
+#define	AI_BLOCKED				0x04000000	// used by blocked_checkattack: set to say I'm attacking while blocked 
+											// (prevents run-attacks)
+//ROGUE
+
+//monster attack state
+#define AS_STRAIGHT				1
+#define AS_SLIDING				2
+#define	AS_MELEE				3
+#define	AS_MISSILE				4
+#define	AS_BLIND				5	// PMM - used by boss code to do nasty things even if it can't see you
+
+// armor types
+#define ARMOR_NONE				0
+#define ARMOR_JACKET			1
+#define ARMOR_COMBAT			2
+#define ARMOR_BODY				3
+#define ARMOR_SHARD				4
+
+// power armor types
+#define POWER_ARMOR_NONE		0
+#define POWER_ARMOR_SCREEN		1
+#define POWER_ARMOR_SHIELD		2
+
+// handedness values
+#define RIGHT_HANDED			0
+#define LEFT_HANDED				1
+#define CENTER_HANDED			2
+
+
+// game.serverflags values
+#define SFL_CROSS_TRIGGER_1		0x00000001
+#define SFL_CROSS_TRIGGER_2		0x00000002
+#define SFL_CROSS_TRIGGER_3		0x00000004
+#define SFL_CROSS_TRIGGER_4		0x00000008
+#define SFL_CROSS_TRIGGER_5		0x00000010
+#define SFL_CROSS_TRIGGER_6		0x00000020
+#define SFL_CROSS_TRIGGER_7		0x00000040
+#define SFL_CROSS_TRIGGER_8		0x00000080
+#define SFL_CROSS_TRIGGER_MASK	0x000000ff
+
+
+// noise types for PlayerNoise
+#define PNOISE_SELF				0
+#define PNOISE_WEAPON			1
+#define PNOISE_IMPACT			2
+
+
+// edict->movetype values
+typedef enum
+{
+MOVETYPE_NONE,			// never moves
+MOVETYPE_NOCLIP,		// origin and angles change with no interaction
+MOVETYPE_PUSH,			// no clip to world, push on box contact
+MOVETYPE_STOP,			// no clip to world, stops on box contact
+
+MOVETYPE_WALK,			// gravity
+MOVETYPE_STEP,			// gravity, special edge handling
+MOVETYPE_FLY,
+MOVETYPE_TOSS,			// gravity
+MOVETYPE_FLYMISSILE,	// extra size to monsters
+MOVETYPE_BOUNCE,
+MOVETYPE_NEWTOSS		// PGM - for deathball
+} movetype_t;
+
+
+
+typedef struct
+{
+	int		base_count;
+	int		max_count;
+	float	normal_protection;
+	float	energy_protection;
+	int		armor;
+} gitem_armor_t;
+
+
+// gitem_t->flags
+#define	IT_WEAPON			0x00000001		// use makes active weapon
+#define	IT_AMMO				0x00000002
+#define IT_ARMOR			0x00000004
+#define IT_STAY_COOP		0x00000008
+#define IT_KEY				0x00000010
+#define IT_POWERUP			0x00000020
+
+// ROGUE
+#define IT_MELEE			0x00000040
+#define IT_NOT_GIVEABLE		0x00000080	// item can not be given
+// ROGUE
+
+// gitem_t->weapmodel for weapons indicates model index
+#define WEAP_BLASTER			1 
+#define WEAP_SHOTGUN			2 
+#define WEAP_SUPERSHOTGUN		3 
+#define WEAP_MACHINEGUN			4 
+#define WEAP_CHAINGUN			5 
+#define WEAP_GRENADES			6 
+#define WEAP_GRENADELAUNCHER	7 
+#define WEAP_ROCKETLAUNCHER		8 
+#define WEAP_HYPERBLASTER		9 
+#define WEAP_RAILGUN			10
+#define WEAP_BFG				11
+
+#define WEAP_DISRUPTOR			12		// PGM
+#define WEAP_ETFRIFLE			13		// PGM
+#define WEAP_PLASMA				14		// PGM
+#define WEAP_PROXLAUNCH			15		// PGM
+#define WEAP_CHAINFIST			16		// PGM
+
+typedef struct gitem_s
+{
+	char		*classname;	// spawning name
+	qboolean	(*pickup)(edict_t *ent, edict_t *other);
+	void		(*use)(edict_t *ent, struct gitem_s *item);
+	void		(*drop)(edict_t *ent, struct gitem_s *item);
+	void		(*weaponthink)(edict_t *ent);
+	char		*pickup_sound;
+	char		*world_model;
+	int			world_model_flags;
+	char		*view_model;
+
+	// client side info
+	char		*icon;
+	char		*pickup_name;	// for printing on pickup
+	int			count_width;		// number of digits to display by icon
+
+	int			quantity;		// for ammo how much, for weapons how much is used per shot
+	char		*ammo;			// for weapons
+	int			flags;			// IT_* flags
+
+	int			weapmodel;		// weapon model index (for weapons)
+
+	void		*info;
+	int			tag;
+
+	char		*precaches;		// string of all models, sounds, and images this item will use
+} gitem_t;
+
+
+
+//
+// this structure is left intact through an entire game
+// it should be initialized at dll load time, and read/written to
+// the server.ssv file for savegames
+//
+typedef struct
+{
+	char		helpmessage1[512];
+	char		helpmessage2[512];
+	int			helpchanged;	// flash F1 icon if non 0, play sound
+								// and increment only if 1, 2, or 3
+
+	gclient_t	*clients;		// [maxclients]
+
+	// can't store spawnpoint in level, because
+	// it would get overwritten by the savegame restore
+	char		spawnpoint[512];	// needed for coop respawns
+
+	// store latched cvars here that we want to get at often
+	int			maxclients;
+	int			maxentities;
+
+	// cross level triggers
+	int			serverflags;
+
+	// items
+	int			num_items;
+
+	qboolean	autosaved;
+} game_locals_t;
+
+
+//
+// this structure is cleared as each map is entered
+// it is read/written to the level.sav file for savegames
+//
+typedef struct
+{
+	int			framenum;
+	float		time;
+
+	char		level_name[MAX_QPATH];	// the descriptive name (Outer Base, etc)
+	char		mapname[MAX_QPATH];		// the server name (base1, etc)
+	char		nextmap[MAX_QPATH];		// go here when fraglimit is hit
+
+	// intermission state
+	float		intermissiontime;		// time the intermission was started
+	char		*changemap;
+	int			exitintermission;
+	vec3_t		intermission_origin;
+	vec3_t		intermission_angle;
+
+	edict_t		*sight_client;	// changed once each frame for coop games
+
+	edict_t		*sight_entity;
+	int			sight_entity_framenum;
+	edict_t		*sound_entity;
+	int			sound_entity_framenum;
+	edict_t		*sound2_entity;
+	int			sound2_entity_framenum;
+
+	int			pic_health;
+
+	int			total_secrets;
+	int			found_secrets;
+
+	int			total_goals;
+	int			found_goals;
+
+	int			total_monsters;
+	int			killed_monsters;
+
+	edict_t		*current_entity;	// entity running from G_RunFrame
+	int			body_que;			// dead bodies
+
+	int			power_cubes;		// ugly necessity for coop
+
+	// ROGUE
+	edict_t		*disguise_violator;
+	int			disguise_violation_framenum;
+	// ROGUE
+} level_locals_t;
+
+
+// spawn_temp_t is only used to hold entity field values that
+// can be set from the editor, but aren't actualy present
+// in edict_t during gameplay
+typedef struct
+{
+	// world vars
+	char		*sky;
+	float		skyrotate;
+	vec3_t		skyaxis;
+	char		*nextmap;
+
+	int			lip;
+	int			distance;
+	int			height;
+	char		*noise;
+	float		pausetime;
+	char		*item;
+	char		*gravity;
+
+	float		minyaw;
+	float		maxyaw;
+	float		minpitch;
+	float		maxpitch;
+} spawn_temp_t;
+
+
+typedef struct
+{
+	// fixed data
+	vec3_t		start_origin;
+	vec3_t		start_angles;
+	vec3_t		end_origin;
+	vec3_t		end_angles;
+
+	int			sound_start;
+	int			sound_middle;
+	int			sound_end;
+
+	float		accel;
+	float		speed;
+	float		decel;
+	float		distance;
+
+	float		wait;
+
+	// state data
+	int			state;
+	vec3_t		dir;
+	float		current_speed;
+	float		move_speed;
+	float		next_speed;
+	float		remaining_distance;
+	float		decel_distance;
+	void		(*endfunc)(edict_t *);
+} moveinfo_t;
+
+
+typedef struct
+{
+	void	(*aifunc)(edict_t *self, float dist);
+	float	dist;
+	void	(*thinkfunc)(edict_t *self);
+} mframe_t;
+
+typedef struct
+{
+	int			firstframe;
+	int			lastframe;
+	mframe_t	*frame;
+	void		(*endfunc)(edict_t *self);
+} mmove_t;
+
+typedef struct
+{
+	mmove_t		*currentmove;
+	unsigned int	aiflags;		// PGM - unsigned, since we're close to the max
+	int			nextframe;
+	float		scale;
+
+	void		(*stand)(edict_t *self);
+	void		(*idle)(edict_t *self);
+	void		(*search)(edict_t *self);
+	void		(*walk)(edict_t *self);
+	void		(*run)(edict_t *self);
+	void		(*dodge)(edict_t *self, edict_t *other, float eta, trace_t *tr);
+	void		(*attack)(edict_t *self);
+	void		(*melee)(edict_t *self);
+	void		(*sight)(edict_t *self, edict_t *other);
+	qboolean	(*checkattack)(edict_t *self);
+
+	float		pausetime;
+	float		attack_finished;
+
+	vec3_t		saved_goal;
+	float		search_time;
+	float		trail_time;
+	vec3_t		last_sighting;
+	int			attack_state;
+	int			lefty;
+	float		idle_time;
+	int			linkcount;
+
+	int			power_armor_type;
+	int			power_armor_power;
+
+//ROGUE
+	qboolean	(*blocked)(edict_t *self, float dist);
+//	edict_t		*last_hint;			// last hint_path the monster touched
+	float		last_hint_time;		// last time the monster checked for hintpaths.
+	edict_t		*goal_hint;			// which hint_path we're trying to get to
+	int			medicTries;
+	edict_t		*badMedic1, *badMedic2;	// these medics have declared this monster "unhealable"
+	edict_t		*healer;	// this is who is healing this monster
+	void		(*duck)(edict_t *self, float eta);
+	void		(*unduck)(edict_t *self);
+	void		(*sidestep)(edict_t *self);
+	//  while abort_duck would be nice, only monsters which duck but don't sidestep would use it .. only the brain
+	//  not really worth it.  sidestep is an implied abort_duck
+//	void		(*abort_duck)(edict_t *self);
+	float		base_height;
+	float		next_duck_time;
+	float		duck_wait_time;
+	edict_t		*last_player_enemy;
+	// blindfire stuff .. the boolean says whether the monster will do it, and blind_fire_time is the timing
+	// (set in the monster) of the next shot
+	qboolean	blindfire;		// will the monster blindfire?
+	float		blind_fire_delay;
+	vec3_t		blind_fire_target;
+	// used by the spawners to not spawn too much and keep track of #s of monsters spawned
+	int			monster_slots;
+	int			monster_used;
+	edict_t		*commander;
+	// powerup timers, used by widow, our friend
+	float		quad_framenum;
+	float		invincible_framenum;
+	float		double_framenum;
+//ROGUE
+} monsterinfo_t;
+
+// ROGUE
+// this determines how long to wait after a duck to duck again.  this needs to be longer than
+// the time after the monster_duck_up in all of the animation sequences
+#define	DUCK_INTERVAL	0.5
+// ROGUE
+
+extern	game_locals_t	game;
+extern	level_locals_t	level;
+extern	game_import_t	gi;
+extern	game_export_t	globals;
+extern	spawn_temp_t	st;
+
+extern	int	sm_meat_index;
+extern	int	snd_fry;
+
+
+// means of death
+#define MOD_UNKNOWN			0
+#define MOD_BLASTER			1
+#define MOD_SHOTGUN			2
+#define MOD_SSHOTGUN		3
+#define MOD_MACHINEGUN		4
+#define MOD_CHAINGUN		5
+#define MOD_GRENADE			6
+#define MOD_G_SPLASH		7
+#define MOD_ROCKET			8
+#define MOD_R_SPLASH		9
+#define MOD_HYPERBLASTER	10
+#define MOD_RAILGUN			11
+#define MOD_BFG_LASER		12
+#define MOD_BFG_BLAST		13
+#define MOD_BFG_EFFECT		14
+#define MOD_HANDGRENADE		15
+#define MOD_HG_SPLASH		16
+#define MOD_WATER			17
+#define MOD_SLIME			18
+#define MOD_LAVA			19
+#define MOD_CRUSH			20
+#define MOD_TELEFRAG		21
+#define MOD_FALLING			22
+#define MOD_SUICIDE			23
+#define MOD_HELD_GRENADE	24
+#define MOD_EXPLOSIVE		25
+#define MOD_BARREL			26
+#define MOD_BOMB			27
+#define MOD_EXIT			28
+#define MOD_SPLASH			29
+#define MOD_TARGET_LASER	30
+#define MOD_TRIGGER_HURT	31
+#define MOD_HIT				32
+#define MOD_TARGET_BLASTER	33
+#define MOD_FRIENDLY_FIRE	0x8000000
+
+//========
+//ROGUE
+#define MOD_CHAINFIST			40
+#define MOD_DISINTEGRATOR		41
+#define MOD_ETF_RIFLE			42
+#define MOD_BLASTER2			43
+#define MOD_HEATBEAM			44
+#define MOD_TESLA				45
+#define MOD_PROX				46
+#define MOD_NUKE				47
+#define MOD_VENGEANCE_SPHERE	48
+#define MOD_HUNTER_SPHERE		49
+#define MOD_DEFENDER_SPHERE		50
+#define MOD_TRACKER				51
+#define MOD_DBALL_CRUSH			52
+#define MOD_DOPPLE_EXPLODE		53
+#define MOD_DOPPLE_VENGEANCE	54
+#define MOD_DOPPLE_HUNTER		55
+//ROGUE
+//========
+
+extern	int	meansOfDeath;
+
+
+extern	edict_t			*g_edicts;
+
+#define	FOFS(x) (int)&(((edict_t *)0)->x)
+#define	STOFS(x) (int)&(((spawn_temp_t *)0)->x)
+#define	LLOFS(x) (int)&(((level_locals_t *)0)->x)
+#define	CLOFS(x) (int)&(((gclient_t *)0)->x)
+
+#define qrandom()	((rand () & 0x7fff) / ((float)0x7fff))
+#define crandom()	(2.0 * (qrandom() - 0.5))
+
+extern	cvar_t	*maxentities;
+extern	cvar_t	*deathmatch;
+extern	cvar_t	*coop;
+extern	cvar_t	*dmflags;
+extern	cvar_t	*skill;
+extern	cvar_t	*fraglimit;
+extern	cvar_t	*timelimit;
+extern	cvar_t	*password;
+extern	cvar_t	*spectator_password;
+extern	cvar_t	*g_select_empty;
+extern	cvar_t	*dedicated;
+
+extern	cvar_t	*filterban;
+
+extern	cvar_t	*sv_gravity;
+extern	cvar_t	*sv_maxvelocity;
+
+extern	cvar_t	*gun_x, *gun_y, *gun_z;
+extern	cvar_t	*sv_rollspeed;
+extern	cvar_t	*sv_rollangle;
+
+extern	cvar_t	*run_pitch;
+extern	cvar_t	*run_roll;
+extern	cvar_t	*bob_up;
+extern	cvar_t	*bob_pitch;
+extern	cvar_t	*bob_roll;
+
+extern	cvar_t	*sv_cheats;
+extern	cvar_t	*maxclients;
+extern	cvar_t	*maxspectators;
+
+extern	cvar_t	*flood_msgs;
+extern	cvar_t	*flood_persecond;
+extern	cvar_t	*flood_waitdelay;
+
+extern	cvar_t	*sv_maplist;
+
+extern	cvar_t	*sv_stopspeed;		// PGM - this was a define in g_phys.c
+
+//ROGUE
+extern	cvar_t	*g_showlogic;
+extern	cvar_t	*gamerules;
+extern	cvar_t	*huntercam;
+extern	cvar_t	*strong_mines;
+extern	cvar_t	*randomrespawn;
+
+// this is for the count of monsters
+#define ENT_SLOTS_LEFT		(ent->monsterinfo.monster_slots - ent->monsterinfo.monster_used)
+#define SELF_SLOTS_LEFT		(self->monsterinfo.monster_slots - self->monsterinfo.monster_used)
+//ROGUE
+
+#define WORLD	(&g_edicts[0])
+
+// item spawnflags
+#define ITEM_TRIGGER_SPAWN		0x00000001
+#define ITEM_NO_TOUCH			0x00000002
+// 6 bits reserved for editor flags
+// 8 bits used as power cube id bits for coop games
+#define DROPPED_ITEM			0x00010000
+#define	DROPPED_PLAYER_ITEM		0x00020000
+#define ITEM_TARGETS_USED		0x00040000
+
+//
+// fields are needed for spawning from the entity string
+// and saving / loading games
+//
+#define FFL_SPAWNTEMP		1
+#define FFL_NOSPAWN			2
+
+typedef enum {
+	F_INT, 
+	F_FLOAT,
+	F_LSTRING,			// string on disk, pointer in memory, TAG_LEVEL
+	F_GSTRING,			// string on disk, pointer in memory, TAG_GAME
+	F_VECTOR,
+	F_ANGLEHACK,
+	F_EDICT,			// index on disk, pointer in memory
+	F_ITEM,				// index on disk, pointer in memory
+	F_CLIENT,			// index on disk, pointer in memory
+	F_FUNCTION,
+	F_MMOVE,
+	F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+	char	*name;
+	int		ofs;
+	fieldtype_t	type;
+	int		flags;
+} field_t;
+
+
+extern	field_t fields[];
+extern	gitem_t	itemlist[];
+
+
+//
+// g_cmds.c
+//
+void Cmd_Help_f (edict_t *ent);
+void Cmd_Score_f (edict_t *ent);
+
+//
+// g_items.c
+//
+void PrecacheItem (gitem_t *it);
+void InitItems (void);
+void SetItemNames (void);
+gitem_t	*FindItem (char *pickup_name);
+gitem_t	*FindItemByClassname (char *classname);
+#define	ITEM_INDEX(x) ((x)-itemlist)
+edict_t *Drop_Item (edict_t *ent, gitem_t *item);
+void SetRespawn (edict_t *ent, float delay);
+void ChangeWeapon (edict_t *ent);
+void SpawnItem (edict_t *ent, gitem_t *item);
+void Think_Weapon (edict_t *ent);
+int ArmorIndex (edict_t *ent);
+int PowerArmorType (edict_t *ent);
+gitem_t	*GetItemByIndex (int index);
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count);
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+//
+// g_utils.c
+//
+qboolean	KillBox (edict_t *ent);
+void	G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+edict_t *G_Find (edict_t *from, int fieldofs, char *match);
+edict_t *findradius (edict_t *from, vec3_t org, float rad);
+edict_t *G_PickTarget (char *targetname);
+void	G_UseTargets (edict_t *ent, edict_t *activator);
+void	G_SetMovedir (vec3_t angles, vec3_t movedir);
+
+void	G_InitEdict (edict_t *e);
+edict_t	*G_Spawn (void);
+void	G_FreeEdict (edict_t *e);
+
+void	G_TouchTriggers (edict_t *ent);
+void	G_TouchSolids (edict_t *ent);
+
+char	*G_CopyString (char *in);
+
+float	*tv (float x, float y, float z);
+char	*vtos (vec3_t v);
+
+float vectoyaw (vec3_t vec);
+void vectoangles (vec3_t vec, vec3_t angles);
+
+//ROGUE
+void	G_ProjectSource2 (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t up, vec3_t result);
+float	vectoyaw2 (vec3_t vec);
+void	vectoangles2 (vec3_t vec, vec3_t angles);
+edict_t *findradius2 (edict_t *from, vec3_t org, float rad);
+//ROGUE
+
+//
+// g_combat.c
+//
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2);
+qboolean CanDamage (edict_t *targ, edict_t *inflictor);
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod);
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod);
+
+//ROGUE
+void T_RadiusNukeDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod);
+void T_RadiusClassDamage (edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, int mod);
+void cleanupHealTarget (edict_t *ent);
+//ROGUE
+
+// damage flags
+#define DAMAGE_RADIUS			0x00000001	// damage was indirect
+#define DAMAGE_NO_ARMOR			0x00000002	// armour does not protect from this damage
+#define DAMAGE_ENERGY			0x00000004	// damage is from an energy based weapon
+#define DAMAGE_NO_KNOCKBACK		0x00000008	// do not affect velocity, just view angles
+#define DAMAGE_BULLET			0x00000010  // damage is from a bullet (used for ricochets)
+#define DAMAGE_NO_PROTECTION	0x00000020  // armor, shields, invulnerability, and godmode have no effect
+//ROGUE
+#define DAMAGE_DESTROY_ARMOR	0x00000040	// damage is done to armor and health.
+#define DAMAGE_NO_REG_ARMOR		0x00000080	// damage skips regular armor
+#define DAMAGE_NO_POWER_ARMOR	0x00000100	// damage skips power armor
+//ROGUE
+
+
+#define DEFAULT_BULLET_HSPREAD	300
+#define DEFAULT_BULLET_VSPREAD	500
+#define DEFAULT_SHOTGUN_HSPREAD	1000
+#define DEFAULT_SHOTGUN_VSPREAD	500
+#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT	12
+#define DEFAULT_SHOTGUN_COUNT	12
+#define DEFAULT_SSHOTGUN_COUNT	20
+
+//
+// g_monster.c
+//
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype);
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype);
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype);
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype);
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype);
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype);
+void M_droptofloor (edict_t *ent);
+void monster_think (edict_t *self);
+void walkmonster_start (edict_t *self);
+void swimmonster_start (edict_t *self);
+void flymonster_start (edict_t *self);
+void AttackFinished (edict_t *self, float time);
+void monster_death_use (edict_t *self);
+void M_CatagorizePosition (edict_t *ent);
+qboolean M_CheckAttack (edict_t *self);
+void M_FlyCheck (edict_t *self);
+void M_CheckGround (edict_t *ent);
+//ROGUE
+void monster_fire_blaster2 (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+void monster_fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy, int flashtype);
+void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, int damage, int kick, int flashtype);
+void stationarymonster_start (edict_t *self);	
+void monster_done_dodge (edict_t *self);
+//ROGUE
+
+
+//
+// g_misc.c
+//
+void ThrowHead (edict_t *self, char *gibname, int damage, int type);
+void ThrowClientHead (edict_t *self, int damage);
+void ThrowGib (edict_t *self, char *gibname, int damage, int type);
+void BecomeExplosion1(edict_t *self);
+
+//
+// g_ai.c
+//
+void AI_SetSightClient (void);
+
+void ai_stand (edict_t *self, float dist);
+void ai_move (edict_t *self, float dist);
+void ai_walk (edict_t *self, float dist);
+void ai_turn (edict_t *self, float dist);
+void ai_run (edict_t *self, float dist);
+void ai_charge (edict_t *self, float dist);
+int range (edict_t *self, edict_t *other);
+
+void FoundTarget (edict_t *self);
+qboolean infront (edict_t *self, edict_t *other);
+qboolean visible (edict_t *self, edict_t *other);
+qboolean FacingIdeal(edict_t *self);
+
+//
+// g_weapon.c
+//
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin);
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick);
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod);
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod);
+void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper);
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius);
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held);
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage);
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);
+
+//
+// g_ptrail.c
+//
+void PlayerTrail_Init (void);
+void PlayerTrail_Add (vec3_t spot);
+void PlayerTrail_New (vec3_t spot);
+edict_t *PlayerTrail_PickFirst (edict_t *self);
+edict_t *PlayerTrail_PickNext (edict_t *self);
+edict_t	*PlayerTrail_LastSpot (void);
+
+//
+// g_client.c
+//
+void respawn (edict_t *ent);
+void BeginIntermission (edict_t *targ);
+void PutClientInServer (edict_t *ent);
+void InitClientPersistant (gclient_t *client);
+void InitClientResp (gclient_t *client);
+void InitBodyQue (void);
+void ClientBeginServerFrame (edict_t *ent);
+
+//
+// g_player.c
+//
+void player_pain (edict_t *self, edict_t *other, float kick, int damage);
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+//
+// g_svcmds.c
+//
+void	ServerCommand (void);
+qboolean SV_FilterPacket (char *from);
+
+//
+// p_view.c
+//
+void ClientEndServerFrame (edict_t *ent);
+
+//
+// p_hud.c
+//
+void MoveClientToIntermission (edict_t *client);
+void G_SetStats (edict_t *ent);
+void G_SetSpectatorStats (edict_t *ent);
+void G_CheckChaseStats (edict_t *ent);
+void ValidateSelectedItem (edict_t *ent);
+void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer);
+
+//
+// g_pweapon.c
+//
+void PlayerNoise(edict_t *who, vec3_t where, int type);
+
+//
+// m_move.c
+//
+qboolean M_CheckBottom (edict_t *ent);
+qboolean M_walkmove (edict_t *ent, float yaw, float dist);
+void M_MoveToGoal (edict_t *ent, float dist);
+void M_ChangeYaw (edict_t *ent);
+
+//
+// g_phys.c
+//
+void G_RunEntity (edict_t *ent);
+
+//
+// g_main.c
+//
+void SaveClientData (void);
+void FetchClientEntData (edict_t *ent);
+
+//
+// g_chase.c
+//
+void UpdateChaseCam(edict_t *ent);
+void ChaseNext(edict_t *ent);
+void ChasePrev(edict_t *ent);
+void GetChaseTarget(edict_t *ent);
+
+
+//====================
+// ROGUE PROTOTYPES
+//
+// g_newweap.c
+//
+//extern float nuke_framenum;
+
+void fire_flechette (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int kick);
+void fire_prox (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed);		
+void fire_nuke (edict_t *self, vec3_t start, vec3_t aimdir, int speed);		
+void fire_flame (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed);
+void fire_burst (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed);
+void fire_maintain (edict_t *, edict_t *, vec3_t start, vec3_t aimdir, int damage, int speed);
+void fire_incendiary_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius);
+void fire_player_melee (edict_t *self, vec3_t start, vec3_t aim, int reach, int damage, int kick, int quiet, int mod);
+void fire_tesla (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed);
+void fire_blaster2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper);
+void fire_heat (edict_t *self, vec3_t start, vec3_t aimdir, vec3_t offset, int damage, int kick, qboolean monster);
+void fire_tracker (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, edict_t *enemy);
+
+//
+// g_newai.c
+//
+qboolean blocked_checkshot (edict_t *self, float shotChance);
+qboolean blocked_checkplat (edict_t *self, float dist);
+qboolean blocked_checkjump (edict_t *self, float dist, float maxDown, float maxUp);
+qboolean blocked_checknewenemy (edict_t *self);
+qboolean monsterlost_checkhint (edict_t *self);
+qboolean inback (edict_t *self, edict_t *other);
+float realrange (edict_t *self, edict_t *other);
+edict_t *SpawnBadArea(vec3_t mins, vec3_t maxs, float lifespan, edict_t *owner);
+edict_t *CheckForBadArea(edict_t *ent);
+qboolean MarkTeslaArea(edict_t *self, edict_t *tesla);
+void InitHintPaths (void);
+void PredictAim (edict_t *target, vec3_t start, float bolt_speed, qboolean eye_height, float offset, vec3_t aimdir, vec3_t aimpoint);
+qboolean below (edict_t *self, edict_t *other);
+void drawbbox (edict_t *self);
+void M_MonsterDodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr);
+void monster_duck_down (edict_t *self);
+void monster_duck_hold (edict_t *self);
+void monster_duck_up (edict_t *self);
+qboolean has_valid_enemy (edict_t *self);
+void TargetTesla (edict_t *self, edict_t *tesla);
+void hintpath_stop (edict_t *self);
+edict_t * PickCoopTarget (edict_t *self);
+int CountPlayers (void);
+void monster_jump_start (edict_t *self);
+qboolean monster_jump_finished (edict_t *self);
+
+
+//
+// g_sphere.c
+//
+void Defender_Launch (edict_t *self);
+void Vengeance_Launch (edict_t *self);
+void Hunter_Launch (edict_t *self);
+
+//
+// g_newdm.c
+//
+void InitGameRules(void);
+edict_t *DoRandomRespawn (edict_t *ent);
+void PrecacheForRandomRespawn (void);
+qboolean Tag_PickupToken (edict_t *ent, edict_t *other);
+void Tag_DropToken (edict_t *ent, gitem_t *item);
+void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+void fire_doppleganger (edict_t *ent, vec3_t start, vec3_t aimdir);
+
+//
+// g_spawn.c
+//
+edict_t *CreateMonster(vec3_t origin, vec3_t angles, char *classname);
+edict_t *CreateFlyMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname);
+edict_t *CreateGroundMonster (vec3_t origin, vec3_t angles, vec3_t mins, vec3_t maxs, char *classname, int height);
+qboolean FindSpawnPoint (vec3_t startpoint, vec3_t mins, vec3_t maxs, vec3_t spawnpoint, float maxMoveUp);
+qboolean CheckSpawnPoint (vec3_t origin, vec3_t mins, vec3_t maxs);
+qboolean CheckGroundSpawnPoint (vec3_t origin, vec3_t entMins, vec3_t entMaxs, float height, float gravity);
+void DetermineBBox (char *classname, vec3_t mins, vec3_t maxs);
+void SpawnGrow_Spawn (vec3_t startpos, int size);
+void Widowlegs_Spawn (vec3_t startpos, vec3_t angles);
+
+//
+// p_client.c
+//
+void RemoveAttackingPainDaemons (edict_t *self);
+
+
+// ROGUE PROTOTYPES
+//====================
+
+//============================================================================
+
+// client_t->anim_priority
+#define	ANIM_BASIC		0		// stand / run
+#define	ANIM_WAVE		1
+#define	ANIM_JUMP		2
+#define	ANIM_PAIN		3
+#define	ANIM_ATTACK		4
+#define	ANIM_DEATH		5
+#define	ANIM_REVERSE	6
+
+
+// client data that stays across multiple level loads
+typedef struct
+{
+	char		userinfo[MAX_INFO_STRING];
+	char		netname[16];
+	int			hand;
+
+	qboolean	connected;			// a loadgame will leave valid entities that
+									// just don't have a connection yet
+
+	// values saved and restored from edicts when changing levels
+	int			health;
+	int			max_health;
+	int			savedFlags;
+
+	int			selected_item;
+	int			inventory[MAX_ITEMS];
+
+	// ammo capacities
+	int			max_bullets;
+	int			max_shells;
+	int			max_rockets;
+	int			max_grenades;
+	int			max_cells;
+	int			max_slugs;
+
+	gitem_t		*weapon;
+	gitem_t		*lastweapon;
+
+	int			power_cubes;	// used for tracking the cubes in coop games
+	int			score;			// for calculating total unit score in coop games
+
+	int			game_helpchanged;
+	int			helpchanged;
+
+	qboolean	spectator;			// client is a spectator
+
+//=========
+//ROGUE
+	int			max_tesla;
+	int			max_prox;
+	int			max_mines;
+	int			max_flechettes;
+#ifndef KILL_DISRUPTOR
+	int			max_rounds;
+#endif
+//ROGUE
+//=========
+} client_persistant_t;
+
+// client data that stays across deathmatch respawns
+typedef struct
+{
+	client_persistant_t	coop_respawn;	// what to set client->pers to on a respawn
+	int			enterframe;			// level.framenum the client entered the game
+	int			score;				// frags, etc
+	vec3_t		cmd_angles;			// angles sent over in the last command
+
+	qboolean	spectator;			// client is a spectator
+} client_respawn_t;
+
+// this structure is cleared on each PutClientInServer(),
+// except for 'client->pers'
+struct gclient_t
+{
+	// known to server
+	player_state_t	ps;				// communicated by server to clients
+	int				ping;
+
+	// private to game
+	client_persistant_t	pers;
+	client_respawn_t	resp;
+	pmove_state_t		old_pmove;	// for detecting out-of-pmove changes
+
+	qboolean	showscores;			// set layout stat
+	qboolean	showinventory;		// set layout stat
+	qboolean	showhelp;
+	qboolean	showhelpicon;
+
+	int			ammo_index;
+
+	int			buttons;
+	int			oldbuttons;
+	int			latched_buttons;
+
+	qboolean	weapon_thunk;
+
+	gitem_t		*newweapon;
+
+	// sum up damage over an entire frame, so
+	// shotgun blasts give a single big kick
+	int			damage_armor;		// damage absorbed by armor
+	int			damage_parmor;		// damage absorbed by power armor
+	int			damage_blood;		// damage taken out of health
+	int			damage_knockback;	// impact damage
+	vec3_t		damage_from;		// origin for vector calculation
+
+	float		killer_yaw;			// when dead, look at killer
+
+	weaponstate_t	weaponstate;
+	vec3_t		kick_angles;	// weapon kicks
+	vec3_t		kick_origin;
+	float		v_dmg_roll, v_dmg_pitch, v_dmg_time;	// damage kicks
+	float		fall_time, fall_value;		// for view drop on fall
+	float		damage_alpha;
+	float		bonus_alpha;
+	vec3_t		damage_blend;
+	vec3_t		v_angle;			// aiming direction
+	float		bobtime;			// so off-ground doesn't change it
+	vec3_t		oldviewangles;
+	vec3_t		oldvelocity;
+
+	float		next_drown_time;
+	int			old_waterlevel;
+	int			breather_sound;
+
+	int			machinegun_shots;	// for weapon raising
+
+	// animation vars
+	int			anim_end;
+	int			anim_priority;
+	qboolean	anim_duck;
+	qboolean	anim_run;
+
+	// powerup timers
+	float		quad_framenum;
+	float		invincible_framenum;
+	float		breather_framenum;
+	float		enviro_framenum;
+
+	qboolean	grenade_blew_up;
+	float		grenade_time;
+	int			silencer_shots;
+	int			weapon_sound;
+
+	float		pickup_msg_time;
+
+	float		flood_locktill;		// locked from talking
+	float		flood_when[10];		// when messages were said
+	int			flood_whenhead;		// head pointer for when said
+
+	float		respawn_time;		// can respawn when time > this
+
+	edict_t		*chase_target;		// player we are chasing
+	qboolean	update_chase;		// need to update chase info?
+
+//=======
+//ROGUE
+	float		double_framenum;
+	float		ir_framenum;
+//	float		torch_framenum;
+	float		nuke_framenum;
+	float		tracker_pain_framenum;
+
+	edict_t		*owned_sphere;		// this points to the player's sphere
+//ROGUE
+//=======
+};
+
+
+struct edict_t
+{
+	entity_state_t	s;
+	gclient_t	*client;	// NULL if not a player
+									// the server expects the first part
+									// of gclient_s to be a player_state_t
+									// but the rest of it is opaque
+
+	qboolean	inuse;
+	int			linkcount;
+
+	// FIXME: move these fields to a server private sv_entity_t
+	link_t		area;				// linked to a division node or leaf
+	
+	int			num_clusters;		// if -1, use headnode instead
+	int			clusternums[MAX_ENT_CLUSTERS];
+	int			headnode;			// unused if num_clusters != -1
+	int			areanum, areanum2;
+
+	//================================
+
+	int			svflags;
+	vec3_t		mins, maxs;
+	vec3_t		absmin, absmax, size;
+	solid_t		solid;
+	int			clipmask;
+	edict_t		*owner;
+
+
+	// DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER
+	// EXPECTS THE FIELDS IN THAT ORDER!
+
+	//================================
+	int			movetype;
+	int			flags;
+
+	char		*model;
+	float		freetime;			// sv.time when the object was freed
+	
+	//
+	// only used locally in game, not by server
+	//
+	char		*message;
+	char		*classname;
+	int			spawnflags;
+
+	float		timestamp;
+
+	float		angle;			// set in qe3, -1 = up, -2 = down
+	char		*target;
+	char		*targetname;
+	char		*killtarget;
+	char		*team;
+	char		*pathtarget;
+	char		*deathtarget;
+	char		*combattarget;
+	edict_t		*target_ent;
+
+	float		speed, accel, decel;
+	vec3_t		movedir;
+	vec3_t		pos1, pos2;
+
+	vec3_t		velocity;
+	vec3_t		avelocity;
+	int			mass;
+	float		air_finished;
+	float		gravity;		// per entity gravity multiplier (1.0 is normal)
+								// use for lowgrav artifact, flares
+
+	edict_t		*goalentity;
+	edict_t		*movetarget;
+	float		yaw_speed;
+	float		ideal_yaw;
+
+	float		nextthink;
+	void		(*prethink) (edict_t *ent);
+	void		(*think)(edict_t *self);
+	void		(*blocked)(edict_t *self, edict_t *other);	//move to moveinfo?
+	void		(*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+	void		(*use)(edict_t *self, edict_t *other, edict_t *activator);
+	void		(*pain)(edict_t *self, edict_t *other, float kick, int damage);
+	void		(*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+	float		touch_debounce_time;		// are all these legit?  do we need more/less of them?
+	float		pain_debounce_time;
+	float		damage_debounce_time;
+	float		fly_sound_debounce_time;	//move to clientinfo
+	float		last_move_time;
+
+	int			health;
+	int			max_health;
+	int			gib_health;
+	int			deadflag;
+	qboolean	show_hostile;
+
+	float		powerarmor_time;
+
+	char		*map;			// target_changelevel
+
+	int			viewheight;		// height above origin where eyesight is determined
+	int			takedamage;
+	int			dmg;
+	int			radius_dmg;
+	float		dmg_radius;
+	int			sounds;			//make this a spawntemp var?
+	int			count;
+
+	edict_t		*chain;
+	edict_t		*enemy;
+	edict_t		*oldenemy;
+	edict_t		*activator;
+	edict_t		*groundentity;
+	int			groundentity_linkcount;
+	edict_t		*teamchain;
+	edict_t		*teammaster;
+
+	edict_t		*mynoise;		// can go in client only
+	edict_t		*mynoise2;
+
+	int			noise_index;
+	int			noise_index2;
+	float		volume;
+	float		attenuation;
+
+	// timing variables
+	float		wait;
+	float		delay;			// before firing targets
+	float		random;
+
+	float		teleport_time;
+
+	int			watertype;
+	int			waterlevel;
+
+	vec3_t		move_origin;
+	vec3_t		move_angles;
+
+	// move this to clientinfo?
+	int			light_level;
+
+	int			style;			// also used as areaportal number
+
+	gitem_t		*item;			// for bonus items
+
+	// common data blocks
+	moveinfo_t		moveinfo;
+	monsterinfo_t	monsterinfo;
+
+//=========
+//ROGUE
+	int			plat2flags;
+	vec3_t		offset;
+	vec3_t		gravityVector;
+	edict_t		*bad_area;
+	edict_t		*hint_chain;
+	edict_t		*monster_hint_chain;
+	edict_t		*target_hint_chain;
+	int			hint_chain_id;
+	// FIXME - debug help!
+	float		lastMoveTime;
+//ROGUE
+//=========
+};
+
+//=============
+//ROGUE
+#define ROGUE_GRAVITY	1
+
+#define SPHERE_DEFENDER			0x0001
+#define SPHERE_HUNTER			0x0002
+#define SPHERE_VENGEANCE		0x0004
+#define SPHERE_DOPPLEGANGER		0x0100
+
+#define SPHERE_TYPE				0x00FF
+#define SPHERE_FLAGS			0xFF00
+
+//
+// deathmatch games
+//
+#define		RDM_TAG			2
+#define		RDM_DEATHBALL	3
+
+typedef struct dm_game_rs
+{
+	void		(*GameInit)(void);
+	void		(*PostInitSetup)(void);
+	void		(*ClientBegin) (edict_t *ent);
+	void		(*SelectSpawnPoint) (edict_t *ent, vec3_t origin, vec3_t angles);
+	void		(*PlayerDeath)(edict_t *targ, edict_t *inflictor, edict_t *attacker);
+	void		(*Score)(edict_t *attacker, edict_t *victim, int scoreChange);
+	void		(*PlayerEffects)(edict_t *ent);
+	void		(*DogTag)(edict_t *ent, edict_t *killer, char **pic);
+	void		(*PlayerDisconnect)(edict_t *ent);
+	int			(*ChangeDamage)(edict_t *targ, edict_t *attacker, int damage, int mod);
+	int			(*ChangeKnockback)(edict_t *targ, edict_t *attacker, int knockback, int mod);
+	int			(*CheckDMRules)(void);
+} dm_game_rt;
+
+extern dm_game_rt	DMGame;
+
+void Tag_GameInit (void);
+void Tag_PostInitSetup (void);
+void Tag_PlayerDeath (edict_t *targ, edict_t *inflictor, edict_t *attacker);
+void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange);
+void Tag_PlayerEffects (edict_t *ent);
+void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic);
+void Tag_PlayerDisconnect (edict_t *ent);
+int  Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod);
+
+void DBall_GameInit (void);
+void DBall_ClientBegin (edict_t *ent);
+void DBall_SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles);
+int  DBall_ChangeKnockback (edict_t *targ, edict_t *attacker, int knockback, int mod);
+int  DBall_ChangeDamage (edict_t *targ, edict_t *attacker, int damage, int mod);
+void DBall_PostInitSetup (void);
+int  DBall_CheckDMRules (void);
+//void Tag_PlayerDeath (edict_t *targ, edict_t *inflictor, edict_t *attacker);
+//void Tag_Score (edict_t *attacker, edict_t *victim, int scoreChange);
+//void Tag_PlayerEffects (edict_t *ent);
+//void Tag_DogTag (edict_t *ent, edict_t *killer, char **pic);
+//void Tag_PlayerDisconnect (edict_t *ent);
+//int  Tag_ChangeDamage (edict_t *targ, edict_t *attacker, int damage);
+
+//ROGUE
+//============
--- /dev/null
+++ b/rogue/m_actor.c
@@ -1,0 +1,592 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_actor.h"
+
+#define	MAX_ACTOR_NAMES		8
+char *actor_names[MAX_ACTOR_NAMES] =
+{
+	"Hellrot",
+	"Tokay",
+	"Killme",
+	"Disruptor",
+	"Adrianator",
+	"Rambear",
+	"Titus",
+	"Bitterman"
+};
+
+
+mframe_t actor_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL};
+
+void actor_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &actor_move_stand;
+
+	// randomize on startup
+	if (level.time < 1.0)
+		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
+}
+
+
+mframe_t actor_frames_walk [] =
+{
+	ai_walk, 0,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL};
+
+void actor_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &actor_move_walk;
+}
+
+
+mframe_t actor_frames_run [] =
+{
+	ai_run, 4,  NULL,
+	ai_run, 15, NULL,
+	ai_run, 15, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 20, NULL,
+	ai_run, 15, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 17, NULL,
+	ai_run, 12, NULL,
+	ai_run, -2, NULL,
+	ai_run, -2, NULL,
+	ai_run, -1, NULL
+};
+mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL};
+
+void actor_run (edict_t *self)
+{
+	if ((level.time < self->pain_debounce_time) && (!self->enemy))
+	{
+		if (self->movetarget)
+			actor_walk(self);
+		else
+			actor_stand(self);
+		return;
+	}
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		actor_stand(self);
+		return;
+	}
+
+	self->monsterinfo.currentmove = &actor_move_run;
+}
+
+
+mframe_t actor_frames_pain1 [] =
+{
+	ai_move, -5, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 1,  NULL
+};
+mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run};
+
+mframe_t actor_frames_pain2 [] =
+{
+	ai_move, -4, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run};
+
+mframe_t actor_frames_pain3 [] =
+{
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run};
+
+mframe_t actor_frames_flipoff [] =
+{
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL
+};
+mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run};
+
+mframe_t actor_frames_taunt [] =
+{
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL
+};
+mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run};
+
+char *messages[] =
+{
+	"Watch it",
+	"#$@*&",
+	"Idiot",
+	"Check your targets"
+};
+
+void actor_pain (edict_t *self, edict_t *other, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+//	gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0);
+
+	if ((other->client) && (qrandom() < 0.4))
+	{
+		vec3_t	v;
+		char	*name;
+
+		VectorSubtract (other->s.origin, self->s.origin, v);
+		self->ideal_yaw = vectoyaw (v);
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &actor_move_flipoff;
+		else
+			self->monsterinfo.currentmove = &actor_move_taunt;
+		name = actor_names[(self - g_edicts)%MAX_ACTOR_NAMES];
+		gi.cprintf (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand()%3]);
+		return;
+	}
+
+	n = rand() % 3;
+	if (n == 0)
+		self->monsterinfo.currentmove = &actor_move_pain1;
+	else if (n == 1)
+		self->monsterinfo.currentmove = &actor_move_pain2;
+	else
+		self->monsterinfo.currentmove = &actor_move_pain3;
+}
+
+
+void actorMachineGun (edict_t *self)
+{
+	vec3_t	start, target;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start);
+	if (self->enemy)
+	{
+		if (self->enemy->health > 0)
+		{
+			VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+			target[2] += self->enemy->viewheight;
+		}
+		else
+		{
+			VectorCopy (self->enemy->absmin, target);
+			target[2] += (self->enemy->size[2] / 2);
+		}
+		VectorSubtract (target, start, forward);
+		VectorNormalize (forward);
+	}
+	else
+	{
+		AngleVectors (self->s.angles, forward, NULL, NULL);
+	}
+	monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1);
+}
+
+
+void actor_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t actor_frames_death1 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -13, NULL,
+	ai_move, 14,  NULL,
+	ai_move, 3,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,   NULL
+};
+mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead};
+
+mframe_t actor_frames_death2 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 7,   NULL,
+	ai_move, -6,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 1,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -9,  NULL,
+	ai_move, -13, NULL,
+	ai_move, -13, NULL,
+	ai_move, 0,   NULL
+};
+mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead};
+
+void actor_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= -80)
+	{
+//		gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+//	gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 2;
+	if (n == 0)
+		self->monsterinfo.currentmove = &actor_move_death1;
+	else
+		self->monsterinfo.currentmove = &actor_move_death2;
+}
+
+
+void actor_fire (edict_t *self)
+{
+	actorMachineGun (self);
+
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t actor_frames_attack [] =
+{
+	ai_charge, -2,  actor_fire,
+	ai_charge, -2,  NULL,
+	ai_charge, 3,   NULL,
+	ai_charge, 2,   NULL
+};
+mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run};
+
+void actor_attack(edict_t *self)
+{
+	int		n;
+
+	self->monsterinfo.currentmove = &actor_move_attack;
+	n = (rand() & 15) + 3 + 7;
+	self->monsterinfo.pausetime = level.time + n * FRAMETIME;
+}
+
+
+void actor_use (edict_t *self, edict_t *, edict_t *)
+{
+	vec3_t		v;
+
+	self->goalentity = self->movetarget = G_PickTarget(self->target);
+	if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0))
+	{
+		gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
+		self->target = NULL;
+		self->monsterinfo.pausetime = 100000000;
+		self->monsterinfo.stand (self);
+		return;
+	}
+
+	VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+	self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+	self->monsterinfo.walk (self);
+	self->target = NULL;
+}
+
+
+/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32)
+*/
+
+void SP_misc_actor (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->targetname)
+	{
+		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("players/male/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	if (!self->health)
+		self->health = 100;
+	self->mass = 200;
+
+	self->pain = actor_pain;
+	self->die = actor_die;
+
+	self->monsterinfo.stand = actor_stand;
+	self->monsterinfo.walk = actor_walk;
+	self->monsterinfo.run = actor_run;
+	self->monsterinfo.attack = actor_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+
+	self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &actor_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+
+	// actors always start in a dormant state, they *must* be used to get going
+	self->use = actor_use;
+}
+
+
+/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL
+JUMP			jump in set direction upon reaching this target
+SHOOT			take a single shot at the pathtarget
+ATTACK			attack pathtarget until it or actor is dead 
+
+"target"		next target_actor
+"pathtarget"	target of any action to be taken at this point
+"wait"			amount of time actor should pause at this point
+"message"		actor will "say" this to the player
+
+for JUMP only:
+"speed"			speed thrown forward (default 200)
+"height"		speed thrown upwards (default 200)
+*/
+
+void target_actor_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t	v;
+
+	if (other->movetarget != self)
+		return;
+	
+	if (other->enemy)
+		return;
+
+	other->goalentity = other->movetarget = NULL;
+
+	if (self->message)
+	{
+		int		n;
+		edict_t	*ent;
+
+		for (n = 1; n <= game.maxclients; n++)
+		{
+			ent = &g_edicts[n];
+			if (!ent->inuse)
+				continue;
+			gi.cprintf (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts)%MAX_ACTOR_NAMES], self->message);
+		}
+	}
+
+	if (self->spawnflags & 1)		//jump
+	{
+		other->velocity[0] = self->movedir[0] * self->speed;
+		other->velocity[1] = self->movedir[1] * self->speed;
+		
+		if (other->groundentity)
+		{
+			other->groundentity = NULL;
+			other->velocity[2] = self->movedir[2];
+			gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0);
+		}
+	}
+
+	if (self->spawnflags & 2)	//shoot
+	{
+	}
+	else if (self->spawnflags & 4)	//attack
+	{
+		other->enemy = G_PickTarget(self->pathtarget);
+		if (other->enemy)
+		{
+			other->goalentity = other->enemy;
+			if (self->spawnflags & 32)
+				other->monsterinfo.aiflags |= AI_BRUTAL;
+			if (self->spawnflags & 16)
+			{
+				other->monsterinfo.aiflags |= AI_STAND_GROUND;
+				actor_stand (other);
+			}
+			else
+			{
+				actor_run (other);
+			}
+		}
+	}
+
+	if (!(self->spawnflags & 6) && (self->pathtarget))
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		G_UseTargets (self, other);
+		self->target = savetarget;
+	}
+
+	other->movetarget = G_PickTarget(self->target);
+
+	if (!other->goalentity)
+		other->goalentity = other->movetarget;
+
+	if (!other->movetarget && !other->enemy)
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.stand (other);
+	}
+	else if (other->movetarget == other->goalentity)
+	{
+		VectorSubtract (other->movetarget->s.origin, other->s.origin, v);
+		other->ideal_yaw = vectoyaw (v);
+	}
+}
+
+void SP_target_actor (edict_t *self)
+{
+	if (!self->targetname)
+		gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin));
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = target_actor_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags = SVF_NOCLIENT;
+
+	if (self->spawnflags & 1)
+	{
+		if (!self->speed)
+			self->speed = 200;
+		if (!st.height)
+			st.height = 200;
+		if (self->s.angles[YAW] == 0)
+			self->s.angles[YAW] = 360;
+		G_SetMovedir (self->s.angles, self->movedir);
+		self->movedir[2] = st.height;
+	}
+
+	gi.linkentity (self);
+}
--- /dev/null
+++ b/rogue/m_actor.h
@@ -1,0 +1,487 @@
+// G:\quake2\baseq2\models/player_y
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak01         	0
+#define FRAME_attak02         	1
+#define FRAME_attak03         	2
+#define FRAME_attak04         	3
+#define FRAME_death101        	4
+#define FRAME_death102        	5
+#define FRAME_death103        	6
+#define FRAME_death104        	7
+#define FRAME_death105        	8
+#define FRAME_death106        	9
+#define FRAME_death107        	10
+#define FRAME_death201        	11
+#define FRAME_death202        	12
+#define FRAME_death203        	13
+#define FRAME_death204        	14
+#define FRAME_death205        	15
+#define FRAME_death206        	16
+#define FRAME_death207        	17
+#define FRAME_death208        	18
+#define FRAME_death209        	19
+#define FRAME_death210        	20
+#define FRAME_death211        	21
+#define FRAME_death212        	22
+#define FRAME_death213        	23
+#define FRAME_death301        	24
+#define FRAME_death302        	25
+#define FRAME_death303        	26
+#define FRAME_death304        	27
+#define FRAME_death305        	28
+#define FRAME_death306        	29
+#define FRAME_death307        	30
+#define FRAME_death308        	31
+#define FRAME_death309        	32
+#define FRAME_death310        	33
+#define FRAME_death311        	34
+#define FRAME_death312        	35
+#define FRAME_death313        	36
+#define FRAME_death314        	37
+#define FRAME_death315        	38
+#define FRAME_flip01          	39
+#define FRAME_flip02          	40
+#define FRAME_flip03          	41
+#define FRAME_flip04          	42
+#define FRAME_flip05          	43
+#define FRAME_flip06          	44
+#define FRAME_flip07          	45
+#define FRAME_flip08          	46
+#define FRAME_flip09          	47
+#define FRAME_flip10          	48
+#define FRAME_flip11          	49
+#define FRAME_flip12          	50
+#define FRAME_flip13          	51
+#define FRAME_flip14          	52
+#define FRAME_grenad01        	53
+#define FRAME_grenad02        	54
+#define FRAME_grenad03        	55
+#define FRAME_grenad04        	56
+#define FRAME_grenad05        	57
+#define FRAME_grenad06        	58
+#define FRAME_grenad07        	59
+#define FRAME_grenad08        	60
+#define FRAME_grenad09        	61
+#define FRAME_grenad10        	62
+#define FRAME_grenad11        	63
+#define FRAME_grenad12        	64
+#define FRAME_grenad13        	65
+#define FRAME_grenad14        	66
+#define FRAME_grenad15        	67
+#define FRAME_jump01          	68
+#define FRAME_jump02          	69
+#define FRAME_jump03          	70
+#define FRAME_jump04          	71
+#define FRAME_jump05          	72
+#define FRAME_jump06          	73
+#define FRAME_pain101         	74
+#define FRAME_pain102         	75
+#define FRAME_pain103         	76
+#define FRAME_pain201         	77
+#define FRAME_pain202         	78
+#define FRAME_pain203         	79
+#define FRAME_pain301         	80
+#define FRAME_pain302         	81
+#define FRAME_pain303         	82
+#define FRAME_push01          	83
+#define FRAME_push02          	84
+#define FRAME_push03          	85
+#define FRAME_push04          	86
+#define FRAME_push05          	87
+#define FRAME_push06          	88
+#define FRAME_push07          	89
+#define FRAME_push08          	90
+#define FRAME_push09          	91
+#define FRAME_run01           	92
+#define FRAME_run02           	93
+#define FRAME_run03           	94
+#define FRAME_run04           	95
+#define FRAME_run05           	96
+#define FRAME_run06           	97
+#define FRAME_run07           	98
+#define FRAME_run08           	99
+#define FRAME_run09           	100
+#define FRAME_run10           	101
+#define FRAME_run11           	102
+#define FRAME_run12           	103
+#define FRAME_runs01          	104
+#define FRAME_runs02          	105
+#define FRAME_runs03          	106
+#define FRAME_runs04          	107
+#define FRAME_runs05          	108
+#define FRAME_runs06          	109
+#define FRAME_runs07          	110
+#define FRAME_runs08          	111
+#define FRAME_runs09          	112
+#define FRAME_runs10          	113
+#define FRAME_runs11          	114
+#define FRAME_runs12          	115
+#define FRAME_salute01        	116
+#define FRAME_salute02        	117
+#define FRAME_salute03        	118
+#define FRAME_salute04        	119
+#define FRAME_salute05        	120
+#define FRAME_salute06        	121
+#define FRAME_salute07        	122
+#define FRAME_salute08        	123
+#define FRAME_salute09        	124
+#define FRAME_salute10        	125
+#define FRAME_salute11        	126
+#define FRAME_salute12        	127
+#define FRAME_stand101        	128
+#define FRAME_stand102        	129
+#define FRAME_stand103        	130
+#define FRAME_stand104        	131
+#define FRAME_stand105        	132
+#define FRAME_stand106        	133
+#define FRAME_stand107        	134
+#define FRAME_stand108        	135
+#define FRAME_stand109        	136
+#define FRAME_stand110        	137
+#define FRAME_stand111        	138
+#define FRAME_stand112        	139
+#define FRAME_stand113        	140
+#define FRAME_stand114        	141
+#define FRAME_stand115        	142
+#define FRAME_stand116        	143
+#define FRAME_stand117        	144
+#define FRAME_stand118        	145
+#define FRAME_stand119        	146
+#define FRAME_stand120        	147
+#define FRAME_stand121        	148
+#define FRAME_stand122        	149
+#define FRAME_stand123        	150
+#define FRAME_stand124        	151
+#define FRAME_stand125        	152
+#define FRAME_stand126        	153
+#define FRAME_stand127        	154
+#define FRAME_stand128        	155
+#define FRAME_stand129        	156
+#define FRAME_stand130        	157
+#define FRAME_stand131        	158
+#define FRAME_stand132        	159
+#define FRAME_stand133        	160
+#define FRAME_stand134        	161
+#define FRAME_stand135        	162
+#define FRAME_stand136        	163
+#define FRAME_stand137        	164
+#define FRAME_stand138        	165
+#define FRAME_stand139        	166
+#define FRAME_stand140        	167
+#define FRAME_stand201        	168
+#define FRAME_stand202        	169
+#define FRAME_stand203        	170
+#define FRAME_stand204        	171
+#define FRAME_stand205        	172
+#define FRAME_stand206        	173
+#define FRAME_stand207        	174
+#define FRAME_stand208        	175
+#define FRAME_stand209        	176
+#define FRAME_stand210        	177
+#define FRAME_stand211        	178
+#define FRAME_stand212        	179
+#define FRAME_stand213        	180
+#define FRAME_stand214        	181
+#define FRAME_stand215        	182
+#define FRAME_stand216        	183
+#define FRAME_stand217        	184
+#define FRAME_stand218        	185
+#define FRAME_stand219        	186
+#define FRAME_stand220        	187
+#define FRAME_stand221        	188
+#define FRAME_stand222        	189
+#define FRAME_stand223        	190
+#define FRAME_swim01          	191
+#define FRAME_swim02          	192
+#define FRAME_swim03          	193
+#define FRAME_swim04          	194
+#define FRAME_swim05          	195
+#define FRAME_swim06          	196
+#define FRAME_swim07          	197
+#define FRAME_swim08          	198
+#define FRAME_swim09          	199
+#define FRAME_swim10          	200
+#define FRAME_swim11          	201
+#define FRAME_swim12          	202
+#define FRAME_sw_atk01        	203
+#define FRAME_sw_atk02        	204
+#define FRAME_sw_atk03        	205
+#define FRAME_sw_atk04        	206
+#define FRAME_sw_atk05        	207
+#define FRAME_sw_atk06        	208
+#define FRAME_sw_pan01        	209
+#define FRAME_sw_pan02        	210
+#define FRAME_sw_pan03        	211
+#define FRAME_sw_pan04        	212
+#define FRAME_sw_pan05        	213
+#define FRAME_sw_std01        	214
+#define FRAME_sw_std02        	215
+#define FRAME_sw_std03        	216
+#define FRAME_sw_std04        	217
+#define FRAME_sw_std05        	218
+#define FRAME_sw_std06        	219
+#define FRAME_sw_std07        	220
+#define FRAME_sw_std08        	221
+#define FRAME_sw_std09        	222
+#define FRAME_sw_std10        	223
+#define FRAME_sw_std11        	224
+#define FRAME_sw_std12        	225
+#define FRAME_sw_std13        	226
+#define FRAME_sw_std14        	227
+#define FRAME_sw_std15        	228
+#define FRAME_sw_std16        	229
+#define FRAME_sw_std17        	230
+#define FRAME_sw_std18        	231
+#define FRAME_sw_std19        	232
+#define FRAME_sw_std20        	233
+#define FRAME_taunt01         	234
+#define FRAME_taunt02         	235
+#define FRAME_taunt03         	236
+#define FRAME_taunt04         	237
+#define FRAME_taunt05         	238
+#define FRAME_taunt06         	239
+#define FRAME_taunt07         	240
+#define FRAME_taunt08         	241
+#define FRAME_taunt09         	242
+#define FRAME_taunt10         	243
+#define FRAME_taunt11         	244
+#define FRAME_taunt12         	245
+#define FRAME_taunt13         	246
+#define FRAME_taunt14         	247
+#define FRAME_taunt15         	248
+#define FRAME_taunt16         	249
+#define FRAME_taunt17         	250
+#define FRAME_walk01          	251
+#define FRAME_walk02          	252
+#define FRAME_walk03          	253
+#define FRAME_walk04          	254
+#define FRAME_walk05          	255
+#define FRAME_walk06          	256
+#define FRAME_walk07          	257
+#define FRAME_walk08          	258
+#define FRAME_walk09          	259
+#define FRAME_walk10          	260
+#define FRAME_walk11          	261
+#define FRAME_wave01          	262
+#define FRAME_wave02          	263
+#define FRAME_wave03          	264
+#define FRAME_wave04          	265
+#define FRAME_wave05          	266
+#define FRAME_wave06          	267
+#define FRAME_wave07          	268
+#define FRAME_wave08          	269
+#define FRAME_wave09          	270
+#define FRAME_wave10          	271
+#define FRAME_wave11          	272
+#define FRAME_wave12          	273
+#define FRAME_wave13          	274
+#define FRAME_wave14          	275
+#define FRAME_wave15          	276
+#define FRAME_wave16          	277
+#define FRAME_wave17          	278
+#define FRAME_wave18          	279
+#define FRAME_wave19          	280
+#define FRAME_wave20          	281
+#define FRAME_wave21          	282
+#define FRAME_bl_atk01        	283
+#define FRAME_bl_atk02        	284
+#define FRAME_bl_atk03        	285
+#define FRAME_bl_atk04        	286
+#define FRAME_bl_atk05        	287
+#define FRAME_bl_atk06        	288
+#define FRAME_bl_flp01        	289
+#define FRAME_bl_flp02        	290
+#define FRAME_bl_flp13        	291
+#define FRAME_bl_flp14        	292
+#define FRAME_bl_flp15        	293
+#define FRAME_bl_jmp01        	294
+#define FRAME_bl_jmp02        	295
+#define FRAME_bl_jmp03        	296
+#define FRAME_bl_jmp04        	297
+#define FRAME_bl_jmp05        	298
+#define FRAME_bl_jmp06        	299
+#define FRAME_bl_pn101        	300
+#define FRAME_bl_pn102        	301
+#define FRAME_bl_pn103        	302
+#define FRAME_bl_pn201        	303
+#define FRAME_bl_pn202        	304
+#define FRAME_bl_pn203        	305
+#define FRAME_bl_pn301        	306
+#define FRAME_bl_pn302        	307
+#define FRAME_bl_pn303        	308
+#define FRAME_bl_psh08        	309
+#define FRAME_bl_psh09        	310
+#define FRAME_bl_run01        	311
+#define FRAME_bl_run02        	312
+#define FRAME_bl_run03        	313
+#define FRAME_bl_run04        	314
+#define FRAME_bl_run05        	315
+#define FRAME_bl_run06        	316
+#define FRAME_bl_run07        	317
+#define FRAME_bl_run08        	318
+#define FRAME_bl_run09        	319
+#define FRAME_bl_run10        	320
+#define FRAME_bl_run11        	321
+#define FRAME_bl_run12        	322
+#define FRAME_bl_rns03        	323
+#define FRAME_bl_rns04        	324
+#define FRAME_bl_rns05        	325
+#define FRAME_bl_rns06        	326
+#define FRAME_bl_rns07        	327
+#define FRAME_bl_rns08        	328
+#define FRAME_bl_rns09        	329
+#define FRAME_bl_sal10        	330
+#define FRAME_bl_sal11        	331
+#define FRAME_bl_sal12        	332
+#define FRAME_bl_std01        	333
+#define FRAME_bl_std02        	334
+#define FRAME_bl_std03        	335
+#define FRAME_bl_std04        	336
+#define FRAME_bl_std05        	337
+#define FRAME_bl_std06        	338
+#define FRAME_bl_std07        	339
+#define FRAME_bl_std08        	340
+#define FRAME_bl_std09        	341
+#define FRAME_bl_std10        	342
+#define FRAME_bl_std11        	343
+#define FRAME_bl_std12        	344
+#define FRAME_bl_std13        	345
+#define FRAME_bl_std14        	346
+#define FRAME_bl_std15        	347
+#define FRAME_bl_std16        	348
+#define FRAME_bl_std17        	349
+#define FRAME_bl_std18        	350
+#define FRAME_bl_std19        	351
+#define FRAME_bl_std20        	352
+#define FRAME_bl_std21        	353
+#define FRAME_bl_std22        	354
+#define FRAME_bl_std23        	355
+#define FRAME_bl_std24        	356
+#define FRAME_bl_std25        	357
+#define FRAME_bl_std26        	358
+#define FRAME_bl_std27        	359
+#define FRAME_bl_std28        	360
+#define FRAME_bl_std29        	361
+#define FRAME_bl_std30        	362
+#define FRAME_bl_std31        	363
+#define FRAME_bl_std32        	364
+#define FRAME_bl_std33        	365
+#define FRAME_bl_std34        	366
+#define FRAME_bl_std35        	367
+#define FRAME_bl_std36        	368
+#define FRAME_bl_std37        	369
+#define FRAME_bl_std38        	370
+#define FRAME_bl_std39        	371
+#define FRAME_bl_std40        	372
+#define FRAME_bl_swm01        	373
+#define FRAME_bl_swm02        	374
+#define FRAME_bl_swm03        	375
+#define FRAME_bl_swm04        	376
+#define FRAME_bl_swm05        	377
+#define FRAME_bl_swm06        	378
+#define FRAME_bl_swm07        	379
+#define FRAME_bl_swm08        	380
+#define FRAME_bl_swm09        	381
+#define FRAME_bl_swm10        	382
+#define FRAME_bl_swm11        	383
+#define FRAME_bl_swm12        	384
+#define FRAME_bl_swk01        	385
+#define FRAME_bl_swk02        	386
+#define FRAME_bl_swk03        	387
+#define FRAME_bl_swk04        	388
+#define FRAME_bl_swk05        	389
+#define FRAME_bl_swk06        	390
+#define FRAME_bl_swp01        	391
+#define FRAME_bl_swp02        	392
+#define FRAME_bl_swp03        	393
+#define FRAME_bl_swp04        	394
+#define FRAME_bl_swp05        	395
+#define FRAME_bl_sws01        	396
+#define FRAME_bl_sws02        	397
+#define FRAME_bl_sws03        	398
+#define FRAME_bl_sws04        	399
+#define FRAME_bl_sws05        	400
+#define FRAME_bl_sws06        	401
+#define FRAME_bl_sws07        	402
+#define FRAME_bl_sws08        	403
+#define FRAME_bl_sws09        	404
+#define FRAME_bl_sws10        	405
+#define FRAME_bl_sws11        	406
+#define FRAME_bl_sws12        	407
+#define FRAME_bl_sws13        	408
+#define FRAME_bl_sws14        	409
+#define FRAME_bl_tau14        	410
+#define FRAME_bl_tau15        	411
+#define FRAME_bl_tau16        	412
+#define FRAME_bl_tau17        	413
+#define FRAME_bl_wlk01        	414
+#define FRAME_bl_wlk02        	415
+#define FRAME_bl_wlk03        	416
+#define FRAME_bl_wlk04        	417
+#define FRAME_bl_wlk05        	418
+#define FRAME_bl_wlk06        	419
+#define FRAME_bl_wlk07        	420
+#define FRAME_bl_wlk08        	421
+#define FRAME_bl_wlk09        	422
+#define FRAME_bl_wlk10        	423
+#define FRAME_bl_wlk11        	424
+#define FRAME_bl_wav19        	425
+#define FRAME_bl_wav20        	426
+#define FRAME_bl_wav21        	427
+#define FRAME_cr_atk01        	428
+#define FRAME_cr_atk02        	429
+#define FRAME_cr_atk03        	430
+#define FRAME_cr_atk04        	431
+#define FRAME_cr_atk05        	432
+#define FRAME_cr_atk06        	433
+#define FRAME_cr_atk07        	434
+#define FRAME_cr_atk08        	435
+#define FRAME_cr_pan01        	436
+#define FRAME_cr_pan02        	437
+#define FRAME_cr_pan03        	438
+#define FRAME_cr_pan04        	439
+#define FRAME_cr_std01        	440
+#define FRAME_cr_std02        	441
+#define FRAME_cr_std03        	442
+#define FRAME_cr_std04        	443
+#define FRAME_cr_std05        	444
+#define FRAME_cr_std06        	445
+#define FRAME_cr_std07        	446
+#define FRAME_cr_std08        	447
+#define FRAME_cr_wlk01        	448
+#define FRAME_cr_wlk02        	449
+#define FRAME_cr_wlk03        	450
+#define FRAME_cr_wlk04        	451
+#define FRAME_cr_wlk05        	452
+#define FRAME_cr_wlk06        	453
+#define FRAME_cr_wlk07        	454
+#define FRAME_crbl_a01        	455
+#define FRAME_crbl_a02        	456
+#define FRAME_crbl_a03        	457
+#define FRAME_crbl_a04        	458
+#define FRAME_crbl_a05        	459
+#define FRAME_crbl_a06        	460
+#define FRAME_crbl_a07        	461
+#define FRAME_crbl_p01        	462
+#define FRAME_crbl_p02        	463
+#define FRAME_crbl_p03        	464
+#define FRAME_crbl_p04        	465
+#define FRAME_crbl_s01        	466
+#define FRAME_crbl_s02        	467
+#define FRAME_crbl_s03        	468
+#define FRAME_crbl_s04        	469
+#define FRAME_crbl_s05        	470
+#define FRAME_crbl_s06        	471
+#define FRAME_crbl_s07        	472
+#define FRAME_crbl_s08        	473
+#define FRAME_crbl_w01        	474
+#define FRAME_crbl_w02        	475
+#define FRAME_crbl_w03        	476
+#define FRAME_crbl_w04        	477
+#define FRAME_crbl_w05        	478
+#define FRAME_crbl_w06        	479
+#define FRAME_crbl_w07        	480
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_berserk.c
@@ -1,0 +1,554 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_berserk.h"
+
+
+static int sound_pain;
+static int sound_die;
+static int sound_idle;
+static int sound_punch;
+static int sound_sight;
+static int sound_search;
+
+void berserk_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void berserk_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+void berserk_fidget (edict_t *self);
+mframe_t berserk_frames_stand [] =
+{
+	ai_stand, 0, berserk_fidget,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t berserk_move_stand = {FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL};
+
+void berserk_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &berserk_move_stand;
+}
+
+mframe_t berserk_frames_stand_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t berserk_move_stand_fidget = {FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand};
+
+void berserk_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() > 0.15)
+		return;
+
+	self->monsterinfo.currentmove = &berserk_move_stand_fidget;
+	gi.sound (self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+mframe_t berserk_frames_walk [] =
+{
+	ai_walk, 9.1, NULL,
+	ai_walk, 6.3, NULL,
+	ai_walk, 4.9, NULL,
+	ai_walk, 6.7, NULL,
+	ai_walk, 6.0, NULL,
+	ai_walk, 8.2, NULL,
+	ai_walk, 7.2, NULL,
+	ai_walk, 6.1, NULL,
+	ai_walk, 4.9, NULL,
+	ai_walk, 4.7, NULL,
+	ai_walk, 4.7, NULL,
+	ai_walk, 4.8, NULL
+};
+mmove_t berserk_move_walk = {FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL};
+
+void berserk_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &berserk_move_walk;
+}
+
+/*
+
+  *****************************
+  SKIPPED THIS FOR NOW!
+  *****************************
+
+   Running -> Arm raised in air
+
+void()	berserk_runb1	=[	$r_att1 ,	berserk_runb2	] {ai_run(21);};
+void()	berserk_runb2	=[	$r_att2 ,	berserk_runb3	] {ai_run(11);};
+void()	berserk_runb3	=[	$r_att3 ,	berserk_runb4	] {ai_run(21);};
+void()	berserk_runb4	=[	$r_att4 ,	berserk_runb5	] {ai_run(25);};
+void()	berserk_runb5	=[	$r_att5 ,	berserk_runb6	] {ai_run(18);};
+void()	berserk_runb6	=[	$r_att6 ,	berserk_runb7	] {ai_run(19);};
+// running with arm in air : start loop
+void()	berserk_runb7	=[	$r_att7 ,	berserk_runb8	] {ai_run(21);};
+void()	berserk_runb8	=[	$r_att8 ,	berserk_runb9	] {ai_run(11);};
+void()	berserk_runb9	=[	$r_att9 ,	berserk_runb10	] {ai_run(21);};
+void()	berserk_runb10	=[	$r_att10 ,	berserk_runb11	] {ai_run(25);};
+void()	berserk_runb11	=[	$r_att11 ,	berserk_runb12	] {ai_run(18);};
+void()	berserk_runb12	=[	$r_att12 ,	berserk_runb7	] {ai_run(19);};
+// running with arm in air : end loop
+*/
+
+
+mframe_t berserk_frames_run1 [] =
+{
+	ai_run, 21, NULL,
+	ai_run, 11, NULL,
+	ai_run, 21, NULL,
+	// PMM .. from NULL
+	ai_run, 25, monster_done_dodge,
+	ai_run, 18, NULL,
+	ai_run, 19, NULL
+};
+mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL};
+
+void berserk_run (edict_t *self)
+{
+	monster_done_dodge (self);
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &berserk_move_stand;
+	else
+		self->monsterinfo.currentmove = &berserk_move_run1;
+}
+
+
+void berserk_attack_spike (edict_t *self)
+{
+	static	vec3_t	aim = {MELEE_DISTANCE, 0, -24};
+	fire_hit (self, aim, (15 + (rand() % 6)), 400);		//	Faster attack -- upwards and backwards
+}
+
+
+void berserk_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
+}
+
+mframe_t berserk_frames_attack_spike [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, berserk_swing,
+		ai_charge, 0, berserk_attack_spike,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run};
+
+
+void berserk_attack_club (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4);
+	fire_hit (self, aim, (5 + (rand() % 6)), 400);		// Slower attack
+}
+
+mframe_t berserk_frames_attack_club [] =
+{	
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, berserk_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, berserk_attack_club,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run};
+
+
+void berserk_strike (edict_t *)
+{
+	//FIXME play impact sound
+}
+
+
+mframe_t berserk_frames_attack_strike [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_swing,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_strike,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 9.7, NULL,
+	ai_move, 13.6, NULL
+};
+	
+mmove_t berserk_move_attack_strike = {FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run};
+
+
+void berserk_melee (edict_t *self)
+{
+	monster_done_dodge (self);
+
+	if ((rand() % 2) == 0)
+		self->monsterinfo.currentmove = &berserk_move_attack_spike;
+	else
+		self->monsterinfo.currentmove = &berserk_move_attack_club;
+}
+
+
+/*
+void() 	berserk_atke1	=[	$r_attb1,	berserk_atke2	] {ai_run(9);};
+void() 	berserk_atke2	=[	$r_attb2,	berserk_atke3	] {ai_run(6);};
+void() 	berserk_atke3	=[	$r_attb3,	berserk_atke4	] {ai_run(18.4);};
+void() 	berserk_atke4	=[	$r_attb4,	berserk_atke5	] {ai_run(25);};
+void() 	berserk_atke5	=[	$r_attb5,	berserk_atke6	] {ai_run(14);};
+void() 	berserk_atke6	=[	$r_attb6,	berserk_atke7	] {ai_run(20);};
+void() 	berserk_atke7	=[	$r_attb7,	berserk_atke8	] {ai_run(8.5);};
+void() 	berserk_atke8	=[	$r_attb8,	berserk_atke9	] {ai_run(3);};
+void() 	berserk_atke9	=[	$r_attb9,	berserk_atke10	] {ai_run(17.5);};
+void() 	berserk_atke10	=[	$r_attb10,	berserk_atke11	] {ai_run(17);};
+void() 	berserk_atke11	=[	$r_attb11,	berserk_atke12	] {ai_run(9);};
+void() 	berserk_atke12	=[	$r_attb12,	berserk_atke13	] {ai_run(25);};
+void() 	berserk_atke13	=[	$r_attb13,	berserk_atke14	] {ai_run(3.7);};
+void() 	berserk_atke14	=[	$r_attb14,	berserk_atke15	] {ai_run(2.6);};
+void() 	berserk_atke15	=[	$r_attb15,	berserk_atke16	] {ai_run(19);};
+void() 	berserk_atke16	=[	$r_attb16,	berserk_atke17	] {ai_run(25);};
+void() 	berserk_atke17	=[	$r_attb17,	berserk_atke18	] {ai_run(19.6);};
+void() 	berserk_atke18	=[	$r_attb18,	berserk_run1	] {ai_run(7.8);};
+*/
+
+
+mframe_t berserk_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_pain1 = {FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run};
+
+
+mframe_t berserk_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_pain2 = {FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run};
+
+void berserk_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	monster_done_dodge (self);
+
+	if ((damage < 20) || (qrandom() < 0.5))
+		self->monsterinfo.currentmove = &berserk_move_pain1;
+	else
+		self->monsterinfo.currentmove = &berserk_move_pain2;
+}
+
+
+void berserk_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+mframe_t berserk_frames_death1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+	
+};
+mmove_t berserk_move_death1 = {FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead};
+
+
+mframe_t berserk_frames_death2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_death2 = {FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead};
+
+
+void berserk_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	if (damage >= 50)
+		self->monsterinfo.currentmove = &berserk_move_death1;
+	else
+		self->monsterinfo.currentmove = &berserk_move_death2;
+}
+
+//===========
+//PGM
+void berserk_jump_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void berserk_jump2_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 150, forward, self->velocity);
+	VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void berserk_jump_wait_land (edict_t *self)
+{
+	if(self->groundentity == NULL)
+	{
+		self->monsterinfo.nextframe = self->s.frame;
+
+		if(monster_jump_finished (self))
+			self->monsterinfo.nextframe = self->s.frame + 1;
+	}
+	else 
+		self->monsterinfo.nextframe = self->s.frame + 1;
+}
+
+mframe_t berserk_frames_jump [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_jump = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run };
+
+mframe_t berserk_frames_jump2 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -4, NULL,
+	ai_move, -4, NULL,
+	ai_move, 0, berserk_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_jump2 = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run };
+
+void berserk_jump (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	monster_done_dodge (self);
+
+	if(self->enemy->s.origin[2] > self->s.origin[2])
+		self->monsterinfo.currentmove = &berserk_move_jump2;
+	else
+		self->monsterinfo.currentmove = &berserk_move_jump;
+}
+
+qboolean berserk_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkjump (self, dist, 256, 40))
+	{
+		berserk_jump(self);
+		return true;
+	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+void berserk_sidestep (edict_t *self)
+{
+	// if we're jumping, don't dodge
+	if ((self->monsterinfo.currentmove == &berserk_move_jump) ||
+		(self->monsterinfo.currentmove == &berserk_move_jump2))
+	{
+		return;
+	}
+
+	// don't check for attack; the eta should suffice for melee monsters
+
+	if (self->monsterinfo.currentmove != &berserk_move_run1)
+		self->monsterinfo.currentmove = &berserk_move_run1;
+}
+
+
+/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_berserk (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// pre-caches
+	sound_pain  = gi.soundindex ("berserk/berpain2.wav");
+	sound_die   = gi.soundindex ("berserk/berdeth2.wav");
+	sound_idle  = gi.soundindex ("berserk/beridle1.wav");
+	sound_punch = gi.soundindex ("berserk/attack.wav");
+	sound_search = gi.soundindex ("berserk/bersrch1.wav");
+	sound_sight = gi.soundindex ("berserk/sight.wav");
+
+	self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 240;
+	self->gib_health = -60;
+	self->mass = 250;
+
+	self->pain = berserk_pain;
+	self->die = berserk_die;
+
+	self->monsterinfo.stand = berserk_stand;
+	self->monsterinfo.walk = berserk_walk;
+	self->monsterinfo.run = berserk_run;
+	// pmm
+//	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.sidestep = berserk_sidestep;
+	// pmm
+	self->monsterinfo.attack = NULL;
+	self->monsterinfo.melee = berserk_melee;
+	self->monsterinfo.sight = berserk_sight;
+	self->monsterinfo.search = berserk_search;
+	self->monsterinfo.blocked = berserk_blocked;		//PGM
+
+	self->monsterinfo.currentmove = &berserk_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	gi.linkentity (self);
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_berserk.h
@@ -1,0 +1,262 @@
+// G:\quake2\baseq2\models/monsters/berserk
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_standb1         	5
+#define FRAME_standb2         	6
+#define FRAME_standb3         	7
+#define FRAME_standb4         	8
+#define FRAME_standb5         	9
+#define FRAME_standb6         	10
+#define FRAME_standb7         	11
+#define FRAME_standb8         	12
+#define FRAME_standb9         	13
+#define FRAME_standb10        	14
+#define FRAME_standb11        	15
+#define FRAME_standb12        	16
+#define FRAME_standb13        	17
+#define FRAME_standb14        	18
+#define FRAME_standb15        	19
+#define FRAME_standb16        	20
+#define FRAME_standb17        	21
+#define FRAME_standb18        	22
+#define FRAME_standb19        	23
+#define FRAME_standb20        	24
+#define FRAME_walkc1          	25
+#define FRAME_walkc2          	26
+#define FRAME_walkc3          	27
+#define FRAME_walkc4          	28
+#define FRAME_walkc5          	29
+#define FRAME_walkc6          	30
+#define FRAME_walkc7          	31
+#define FRAME_walkc8          	32
+#define FRAME_walkc9          	33
+#define FRAME_walkc10         	34
+#define FRAME_walkc11         	35
+#define FRAME_run1            	36
+#define FRAME_run2            	37
+#define FRAME_run3            	38
+#define FRAME_run4            	39
+#define FRAME_run5            	40
+#define FRAME_run6            	41
+#define FRAME_att_a1          	42
+#define FRAME_att_a2          	43
+#define FRAME_att_a3          	44
+#define FRAME_att_a4          	45
+#define FRAME_att_a5          	46
+#define FRAME_att_a6          	47
+#define FRAME_att_a7          	48
+#define FRAME_att_a8          	49
+#define FRAME_att_a9          	50
+#define FRAME_att_a10         	51
+#define FRAME_att_a11         	52
+#define FRAME_att_a12         	53
+#define FRAME_att_a13         	54
+#define FRAME_att_b1          	55
+#define FRAME_att_b2          	56
+#define FRAME_att_b3          	57
+#define FRAME_att_b4          	58
+#define FRAME_att_b5          	59
+#define FRAME_att_b6          	60
+#define FRAME_att_b7          	61
+#define FRAME_att_b8          	62
+#define FRAME_att_b9          	63
+#define FRAME_att_b10         	64
+#define FRAME_att_b11         	65
+#define FRAME_att_b12         	66
+#define FRAME_att_b13         	67
+#define FRAME_att_b14         	68
+#define FRAME_att_b15         	69
+#define FRAME_att_b16         	70
+#define FRAME_att_b17         	71
+#define FRAME_att_b18         	72
+#define FRAME_att_b19         	73
+#define FRAME_att_b20         	74
+#define FRAME_att_b21         	75
+#define FRAME_att_c1          	76
+#define FRAME_att_c2          	77
+#define FRAME_att_c3          	78
+#define FRAME_att_c4          	79
+#define FRAME_att_c5          	80
+#define FRAME_att_c6          	81
+#define FRAME_att_c7          	82
+#define FRAME_att_c8          	83
+#define FRAME_att_c9          	84
+#define FRAME_att_c10         	85
+#define FRAME_att_c11         	86
+#define FRAME_att_c12         	87
+#define FRAME_att_c13         	88
+#define FRAME_att_c14         	89
+#define FRAME_att_c15         	90
+#define FRAME_att_c16         	91
+#define FRAME_att_c17         	92
+#define FRAME_att_c18         	93
+#define FRAME_att_c19         	94
+#define FRAME_att_c20         	95
+#define FRAME_att_c21         	96
+#define FRAME_att_c22         	97
+#define FRAME_att_c23         	98
+#define FRAME_att_c24         	99
+#define FRAME_att_c25         	100
+#define FRAME_att_c26         	101
+#define FRAME_att_c27         	102
+#define FRAME_att_c28         	103
+#define FRAME_att_c29         	104
+#define FRAME_att_c30         	105
+#define FRAME_att_c31         	106
+#define FRAME_att_c32         	107
+#define FRAME_att_c33         	108
+#define FRAME_att_c34         	109
+#define FRAME_r_att1          	110
+#define FRAME_r_att2          	111
+#define FRAME_r_att3          	112
+#define FRAME_r_att4          	113
+#define FRAME_r_att5          	114
+#define FRAME_r_att6          	115
+#define FRAME_r_att7          	116
+#define FRAME_r_att8          	117
+#define FRAME_r_att9          	118
+#define FRAME_r_att10         	119
+#define FRAME_r_att11         	120
+#define FRAME_r_att12         	121
+#define FRAME_r_att13         	122
+#define FRAME_r_att14         	123
+#define FRAME_r_att15         	124
+#define FRAME_r_att16         	125
+#define FRAME_r_att17         	126
+#define FRAME_r_att18         	127
+#define FRAME_r_attb1         	128
+#define FRAME_r_attb2         	129
+#define FRAME_r_attb3         	130
+#define FRAME_r_attb4         	131
+#define FRAME_r_attb5         	132
+#define FRAME_r_attb6         	133
+#define FRAME_r_attb7         	134
+#define FRAME_r_attb8         	135
+#define FRAME_r_attb9         	136
+#define FRAME_r_attb10        	137
+#define FRAME_r_attb11        	138
+#define FRAME_r_attb12        	139
+#define FRAME_r_attb13        	140
+#define FRAME_r_attb14        	141
+#define FRAME_r_attb15        	142
+#define FRAME_r_attb16        	143
+#define FRAME_r_attb17        	144
+#define FRAME_r_attb18        	145
+#define FRAME_slam1           	146
+#define FRAME_slam2           	147
+#define FRAME_slam3           	148
+#define FRAME_slam4           	149
+#define FRAME_slam5           	150
+#define FRAME_slam6           	151
+#define FRAME_slam7           	152
+#define FRAME_slam8           	153
+#define FRAME_slam9           	154
+#define FRAME_slam10          	155
+#define FRAME_slam11          	156
+#define FRAME_slam12          	157
+#define FRAME_slam13          	158
+#define FRAME_slam14          	159
+#define FRAME_slam15          	160
+#define FRAME_slam16          	161
+#define FRAME_slam17          	162
+#define FRAME_slam18          	163
+#define FRAME_slam19          	164
+#define FRAME_slam20          	165
+#define FRAME_slam21          	166
+#define FRAME_slam22          	167
+#define FRAME_slam23          	168
+#define FRAME_duck1           	169
+#define FRAME_duck2           	170
+#define FRAME_duck3           	171
+#define FRAME_duck4           	172
+#define FRAME_duck5           	173
+#define FRAME_duck6           	174
+#define FRAME_duck7           	175
+#define FRAME_duck8           	176
+#define FRAME_duck9           	177
+#define FRAME_duck10          	178
+#define FRAME_fall1           	179
+#define FRAME_fall2           	180
+#define FRAME_fall3           	181
+#define FRAME_fall4           	182
+#define FRAME_fall5           	183
+#define FRAME_fall6           	184
+#define FRAME_fall7           	185
+#define FRAME_fall8           	186
+#define FRAME_fall9           	187
+#define FRAME_fall10          	188
+#define FRAME_fall11          	189
+#define FRAME_fall12          	190
+#define FRAME_fall13          	191
+#define FRAME_fall14          	192
+#define FRAME_fall15          	193
+#define FRAME_fall16          	194
+#define FRAME_fall17          	195
+#define FRAME_fall18          	196
+#define FRAME_fall19          	197
+#define FRAME_fall20          	198
+#define FRAME_painc1          	199
+#define FRAME_painc2          	200
+#define FRAME_painc3          	201
+#define FRAME_painc4          	202
+#define FRAME_painb1          	203
+#define FRAME_painb2          	204
+#define FRAME_painb3          	205
+#define FRAME_painb4          	206
+#define FRAME_painb5          	207
+#define FRAME_painb6          	208
+#define FRAME_painb7          	209
+#define FRAME_painb8          	210
+#define FRAME_painb9          	211
+#define FRAME_painb10         	212
+#define FRAME_painb11         	213
+#define FRAME_painb12         	214
+#define FRAME_painb13         	215
+#define FRAME_painb14         	216
+#define FRAME_painb15         	217
+#define FRAME_painb16         	218
+#define FRAME_painb17         	219
+#define FRAME_painb18         	220
+#define FRAME_painb19         	221
+#define FRAME_painb20         	222
+#define FRAME_death1          	223
+#define FRAME_death2          	224
+#define FRAME_death3          	225
+#define FRAME_death4          	226
+#define FRAME_death5          	227
+#define FRAME_death6          	228
+#define FRAME_death7          	229
+#define FRAME_death8          	230
+#define FRAME_death9          	231
+#define FRAME_death10         	232
+#define FRAME_death11         	233
+#define FRAME_death12         	234
+#define FRAME_death13         	235
+#define FRAME_deathc1         	236
+#define FRAME_deathc2         	237
+#define FRAME_deathc3         	238
+#define FRAME_deathc4         	239
+#define FRAME_deathc5         	240
+#define FRAME_deathc6         	241
+#define FRAME_deathc7         	242
+#define FRAME_deathc8         	243
+
+//PGM
+#define FRAME_jump1				244
+#define FRAME_jump2				245
+#define FRAME_jump3				246
+#define FRAME_jump4				247
+#define FRAME_jump5				248
+#define FRAME_jump6				249
+#define FRAME_jump7				250
+#define FRAME_jump8				251
+#define FRAME_jump9				252
+//PGM
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_boss2.c
@@ -1,0 +1,769 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss2.h"
+
+void BossExplode (edict_t *self);
+
+qboolean infront (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+
+void boss2_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+}
+
+void boss2_run (edict_t *self);
+void boss2_stand (edict_t *self);
+void boss2_dead (edict_t *self);
+void boss2_attack (edict_t *self);
+void boss2_attack_mg (edict_t *self);
+void boss2_reattack_mg (edict_t *self);
+void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+#define BOSS2_ROCKET_SPEED	750
+
+void Boss2PredictiveRocket  (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	float	time, dist;
+
+gi.dprintf("predictive fire\n");
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
+		VectorSubtract(self->enemy->s.origin, start, dir);
+//		dir[2] += self->enemy->viewheight;
+		dist = VectorLength(dir);
+		time = dist / BOSS2_ROCKET_SPEED;
+		VectorMA(self->enemy->s.origin, time-0.3, self->enemy->velocity, vec);
+
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1);
+
+//2
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
+		VectorSubtract(self->enemy->s.origin, start, dir);
+//		dir[2] += self->enemy->viewheight;
+		dist = VectorLength(dir);
+		time = dist / BOSS2_ROCKET_SPEED;
+		VectorMA(self->enemy->s.origin, time-0.15, self->enemy->velocity, vec);
+
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2);
+
+//3
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
+		VectorSubtract(self->enemy->s.origin, start, dir);
+//		dir[2] += self->enemy->viewheight;
+		dist = VectorLength(dir);
+		time = dist / BOSS2_ROCKET_SPEED;
+		VectorMA(self->enemy->s.origin, time, self->enemy->velocity, vec);
+
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3);
+
+//4
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+		VectorSubtract(self->enemy->s.origin, start, dir);
+//		dir[2] += self->enemy->viewheight;
+		dist = VectorLength(dir);
+		time = dist / BOSS2_ROCKET_SPEED;
+		VectorMA(self->enemy->s.origin, time+0.15, self->enemy->velocity, vec);
+
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4);
+}	
+
+void Boss2Rocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	if(self->enemy)
+	{
+		if(self->enemy->client && qrandom() < 0.9)
+		{
+			Boss2PredictiveRocket(self);
+			return;
+		}
+	}
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	vec[2] -= 15;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, 0.4, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
+
+//2
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, 0.025, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+
+//3
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, -0.025, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
+
+//4
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	vec[2] -= 15;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, -0.4, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
+
+//5
+//	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+//	VectorSubtract (vec, start, dir);
+//	VectorNormalize (dir);
+//	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+}	
+
+void boss2_firebullet_right (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start);
+
+//	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
+//	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
+}	
+
+void boss2_firebullet_left (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+	
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start);
+
+//	VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
+//	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
+}	
+
+void Boss2MachineGun (edict_t *self)
+{
+/*	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+	flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+*/
+	boss2_firebullet_left(self);
+	boss2_firebullet_right(self);
+}	
+
+
+mframe_t boss2_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL};
+
+mframe_t boss2_frames_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL};
+
+mframe_t boss2_frames_walk [] =
+{
+	/* PMM - normally, this is all 8's .. I upped it */
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL,
+	ai_walk,	10,	NULL
+};
+mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL};
+
+
+mframe_t boss2_frames_run [] =
+{
+	/* PMM - normally, this is all 8's .. I upped it */
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL
+};
+mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL};
+
+mframe_t boss2_frames_attack_pre_mg [] =
+{
+	/* used to be all 1's .. what a slow guy */
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	boss2_attack_mg
+};
+mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL};
+
+
+// Loop this
+mframe_t boss2_frames_attack_mg [] =
+{
+	/* used to be all 1's .. what a slow guy */
+	ai_charge,	2,	Boss2MachineGun,
+	ai_charge,	2,	Boss2MachineGun,
+	ai_charge,	2,	Boss2MachineGun,
+	ai_charge,	2,	Boss2MachineGun,
+	ai_charge,	2,	Boss2MachineGun,
+	ai_charge,	2,	boss2_reattack_mg
+};
+mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL};
+
+mframe_t boss2_frames_attack_post_mg [] =
+{
+	/* used to be all 1's .. what a slow guy */
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL
+};
+mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run};
+
+mframe_t boss2_frames_attack_rocket [] =
+{
+	/* used to be all 1's .. except the Boss2Rocket frame, which was -20(!) */
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_move,	-5,	Boss2Rocket,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	2,	NULL
+};
+mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run};
+
+mframe_t boss2_frames_pain_heavy [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run};
+
+mframe_t boss2_frames_pain_light [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run};
+
+mframe_t boss2_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode
+};
+mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead};
+
+void boss2_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &boss2_move_stand;
+}
+
+void boss2_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &boss2_move_stand;
+	else
+		self->monsterinfo.currentmove = &boss2_move_run;
+}
+
+void boss2_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &boss2_move_walk;
+}
+
+void boss2_attack (edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+	
+	if (range <= 125)
+	{
+		self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+	}
+	else 
+	{
+		if (qrandom() <= 0.6)
+			self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+		else
+			self->monsterinfo.currentmove = &boss2_move_attack_rocket;
+	}
+}
+
+void boss2_attack_mg (edict_t *self)
+{
+	self->monsterinfo.currentmove = &boss2_move_attack_mg;
+}
+
+void boss2_reattack_mg (edict_t *self)
+{
+	if ( infront(self, self->enemy) )
+		if (qrandom() <= 0.7)
+			self->monsterinfo.currentmove = &boss2_move_attack_mg;
+		else
+			self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+	else
+		self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+}
+
+
+void boss2_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+// American wanted these at no attenuation
+	if (damage < 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_light;
+	}
+	else if (damage < 30)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_light;
+	}
+	else 
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_heavy;
+	}
+}
+
+void boss2_dead (edict_t *self)
+{
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void boss2_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &boss2_move_death;
+/*
+	int		n;
+
+	self->s.sound = 0;
+	// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &boss2_move_death;
+*/
+}
+
+qboolean Boss2_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+				return false;
+		}
+	}
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.8;
+	}
+	else
+	{
+		return false;
+	}
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	if ((qrandom () < chance) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+
+/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_boss2 (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav");
+	sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav");
+	sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav");
+	sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav");
+	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
+
+	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2");
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+
+	self->health = 2000;
+	self->gib_health = -200;
+	self->mass = 1000;
+
+	self->yaw_speed = 50;
+
+	self->flags |= FL_IMMUNE_LASER;
+
+	self->pain = boss2_pain;
+	self->die = boss2_die;
+
+	self->monsterinfo.stand = boss2_stand;
+	self->monsterinfo.walk = boss2_walk;
+	self->monsterinfo.run = boss2_run;
+	self->monsterinfo.attack = boss2_attack;
+	self->monsterinfo.search = boss2_search;
+	self->monsterinfo.checkattack = Boss2_CheckAttack;
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &boss2_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_boss2.h
@@ -1,0 +1,187 @@
+// G:\quake2\baseq2\models/monsters/boss2
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand30         	0
+#define FRAME_stand31         	1
+#define FRAME_stand32         	2
+#define FRAME_stand33         	3
+#define FRAME_stand34         	4
+#define FRAME_stand35         	5
+#define FRAME_stand36         	6
+#define FRAME_stand37         	7
+#define FRAME_stand38         	8
+#define FRAME_stand39         	9
+#define FRAME_stand40         	10
+#define FRAME_stand41         	11
+#define FRAME_stand42         	12
+#define FRAME_stand43         	13
+#define FRAME_stand44         	14
+#define FRAME_stand45         	15
+#define FRAME_stand46         	16
+#define FRAME_stand47         	17
+#define FRAME_stand48         	18
+#define FRAME_stand49         	19
+#define FRAME_stand50         	20
+#define FRAME_stand1          	21
+#define FRAME_stand2          	22
+#define FRAME_stand3          	23
+#define FRAME_stand4          	24
+#define FRAME_stand5          	25
+#define FRAME_stand6          	26
+#define FRAME_stand7          	27
+#define FRAME_stand8          	28
+#define FRAME_stand9          	29
+#define FRAME_stand10         	30
+#define FRAME_stand11         	31
+#define FRAME_stand12         	32
+#define FRAME_stand13         	33
+#define FRAME_stand14         	34
+#define FRAME_stand15         	35
+#define FRAME_stand16         	36
+#define FRAME_stand17         	37
+#define FRAME_stand18         	38
+#define FRAME_stand19         	39
+#define FRAME_stand20         	40
+#define FRAME_stand21         	41
+#define FRAME_stand22         	42
+#define FRAME_stand23         	43
+#define FRAME_stand24         	44
+#define FRAME_stand25         	45
+#define FRAME_stand26         	46
+#define FRAME_stand27         	47
+#define FRAME_stand28         	48
+#define FRAME_stand29         	49
+#define FRAME_walk1           	50
+#define FRAME_walk2           	51
+#define FRAME_walk3           	52
+#define FRAME_walk4           	53
+#define FRAME_walk5           	54
+#define FRAME_walk6           	55
+#define FRAME_walk7           	56
+#define FRAME_walk8           	57
+#define FRAME_walk9           	58
+#define FRAME_walk10          	59
+#define FRAME_walk11          	60
+#define FRAME_walk12          	61
+#define FRAME_walk13          	62
+#define FRAME_walk14          	63
+#define FRAME_walk15          	64
+#define FRAME_walk16          	65
+#define FRAME_walk17          	66
+#define FRAME_walk18          	67
+#define FRAME_walk19          	68
+#define FRAME_walk20          	69
+#define FRAME_attack1         	70
+#define FRAME_attack2         	71
+#define FRAME_attack3         	72
+#define FRAME_attack4         	73
+#define FRAME_attack5         	74
+#define FRAME_attack6         	75
+#define FRAME_attack7         	76
+#define FRAME_attack8         	77
+#define FRAME_attack9         	78
+#define FRAME_attack10        	79
+#define FRAME_attack11        	80
+#define FRAME_attack12        	81
+#define FRAME_attack13        	82
+#define FRAME_attack14        	83
+#define FRAME_attack15        	84
+#define FRAME_attack16        	85
+#define FRAME_attack17        	86
+#define FRAME_attack18        	87
+#define FRAME_attack19        	88
+#define FRAME_attack20        	89
+#define FRAME_attack21        	90
+#define FRAME_attack22        	91
+#define FRAME_attack23        	92
+#define FRAME_attack24        	93
+#define FRAME_attack25        	94
+#define FRAME_attack26        	95
+#define FRAME_attack27        	96
+#define FRAME_attack28        	97
+#define FRAME_attack29        	98
+#define FRAME_attack30        	99
+#define FRAME_attack31        	100
+#define FRAME_attack32        	101
+#define FRAME_attack33        	102
+#define FRAME_attack34        	103
+#define FRAME_attack35        	104
+#define FRAME_attack36        	105
+#define FRAME_attack37        	106
+#define FRAME_attack38        	107
+#define FRAME_attack39        	108
+#define FRAME_attack40        	109
+#define FRAME_pain2           	110
+#define FRAME_pain3           	111
+#define FRAME_pain4           	112
+#define FRAME_pain5           	113
+#define FRAME_pain6           	114
+#define FRAME_pain7           	115
+#define FRAME_pain8           	116
+#define FRAME_pain9           	117
+#define FRAME_pain10          	118
+#define FRAME_pain11          	119
+#define FRAME_pain12          	120
+#define FRAME_pain13          	121
+#define FRAME_pain14          	122
+#define FRAME_pain15          	123
+#define FRAME_pain16          	124
+#define FRAME_pain17          	125
+#define FRAME_pain18          	126
+#define FRAME_pain19          	127
+#define FRAME_pain20          	128
+#define FRAME_pain21          	129
+#define FRAME_pain22          	130
+#define FRAME_pain23          	131
+#define FRAME_death2          	132
+#define FRAME_death3          	133
+#define FRAME_death4          	134
+#define FRAME_death5          	135
+#define FRAME_death6          	136
+#define FRAME_death7          	137
+#define FRAME_death8          	138
+#define FRAME_death9          	139
+#define FRAME_death10         	140
+#define FRAME_death11         	141
+#define FRAME_death12         	142
+#define FRAME_death13         	143
+#define FRAME_death14         	144
+#define FRAME_death15         	145
+#define FRAME_death16         	146
+#define FRAME_death17         	147
+#define FRAME_death18         	148
+#define FRAME_death19         	149
+#define FRAME_death20         	150
+#define FRAME_death21         	151
+#define FRAME_death22         	152
+#define FRAME_death23         	153
+#define FRAME_death24         	154
+#define FRAME_death25         	155
+#define FRAME_death26         	156
+#define FRAME_death27         	157
+#define FRAME_death28         	158
+#define FRAME_death29         	159
+#define FRAME_death30         	160
+#define FRAME_death31         	161
+#define FRAME_death32         	162
+#define FRAME_death33         	163
+#define FRAME_death34         	164
+#define FRAME_death35         	165
+#define FRAME_death36         	166
+#define FRAME_death37         	167
+#define FRAME_death38         	168
+#define FRAME_death39         	169
+#define FRAME_death40         	170
+#define FRAME_death41         	171
+#define FRAME_death42         	172
+#define FRAME_death43         	173
+#define FRAME_death44         	174
+#define FRAME_death45         	175
+#define FRAME_death46         	176
+#define FRAME_death47         	177
+#define FRAME_death48         	178
+#define FRAME_death49         	179
+#define FRAME_death50         	180
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_boss3.c
@@ -1,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss32.h"
+
+void Use_Boss3 (edict_t *ent, edict_t *, edict_t *)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	G_FreeEdict (ent);
+}
+
+void Think_Boss3Stand (edict_t *ent)
+{
+	if (ent->s.frame == FRAME_stand260)
+		ent->s.frame = FRAME_stand201;
+	else
+		ent->s.frame++;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90)
+
+Just stands and cycles in one place until targeted, then teleports away.
+*/
+void SP_monster_boss3_stand (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->model = "models/monsters/boss3/rider/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	self->s.frame = FRAME_stand201;
+
+	gi.soundindex ("misc/bigtele.wav");
+
+	VectorSet (self->mins, -32, -32, 0);
+	VectorSet (self->maxs, 32, 32, 90);
+
+	self->use = Use_Boss3;
+	self->think = Think_Boss3Stand;
+	self->nextthink = level.time + FRAMETIME;
+	gi.linkentity (self);
+}
--- /dev/null
+++ b/rogue/m_boss31.c
@@ -1,0 +1,724 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss31.h"
+
+extern SP_monster_makron (edict_t *self);
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_idle;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_search2;
+static int	sound_search3;
+static int	sound_attack1;
+static int	sound_attack2;
+static int	sound_firegun;
+static int	sound_step_left;
+static int	sound_step_right;
+static int	sound_death_hit;
+
+void BossExplode (edict_t *self);
+void MakronToss (edict_t *self);
+
+
+void jorg_search (edict_t *self)
+{
+	float r;
+
+	r = qrandom();
+
+	if (r <= 0.3)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else if (r <= 0.6)
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0);
+}
+
+
+void jorg_dead (edict_t *self);
+void jorgBFG (edict_t *self);
+void jorgMachineGun (edict_t *self);
+void jorg_firebullet (edict_t *self);
+void jorg_reattack1(edict_t *self);
+void jorg_attack1(edict_t *self);
+void jorg_idle(edict_t *self);
+void jorg_step_left(edict_t *self);
+void jorg_step_right(edict_t *self);
+void jorg_death_hit(edict_t *self);
+
+//
+// stand
+//
+
+mframe_t jorg_frames_stand []=
+{
+	ai_stand, 0, jorg_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 19, NULL,
+	ai_stand, 11, jorg_step_left,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 6, NULL,
+	ai_stand, 9, jorg_step_right,
+	ai_stand, 0, NULL,		// 40
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, -2, NULL,
+	ai_stand, -17, jorg_step_left,
+	ai_stand, 0, NULL,
+	ai_stand, -12, NULL,		// 50
+	ai_stand, -14, jorg_step_right	// 51
+};
+mmove_t	jorg_move_stand = {FRAME_stand01, FRAME_stand51, jorg_frames_stand, NULL};
+
+void jorg_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_NORM,0);
+}
+
+void jorg_death_hit (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM,0);
+}
+
+
+void jorg_step_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0);
+}
+
+void jorg_step_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0);
+}
+
+
+void jorg_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &jorg_move_stand;
+}
+
+mframe_t jorg_frames_run [] =
+{
+	ai_run, 17,	jorg_step_left,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 10,	NULL,
+	ai_run, 33,	jorg_step_right,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 9,	NULL
+};
+mmove_t	jorg_move_run = {FRAME_walk06, FRAME_walk19, jorg_frames_run, NULL};
+
+//
+// walk
+//
+
+mframe_t jorg_frames_start_walk [] =
+{
+	ai_walk,	5,	NULL,
+	ai_walk,	6,	NULL,
+	ai_walk,	7,	NULL,
+	ai_walk,	9,	NULL,
+	ai_walk,	15,	NULL
+};
+mmove_t jorg_move_start_walk = {FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, NULL};
+
+mframe_t jorg_frames_walk [] =
+{
+	ai_walk, 17,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 12,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 10,	NULL,
+	ai_walk, 33,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 9,	NULL
+};
+mmove_t	jorg_move_walk = {FRAME_walk06, FRAME_walk19, jorg_frames_walk, NULL};
+
+mframe_t jorg_frames_end_walk [] =
+{
+	ai_walk,	11,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	-8,	NULL
+};
+mmove_t jorg_move_end_walk = {FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, NULL};
+
+void jorg_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &jorg_move_walk;
+}
+
+void jorg_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &jorg_move_stand;
+	else
+		self->monsterinfo.currentmove = &jorg_move_run;
+}
+
+mframe_t jorg_frames_pain3 [] =
+{
+	ai_move,	-28,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-3,	jorg_step_left,
+	ai_move,	-9,	NULL,
+	ai_move,	0,	jorg_step_right,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-7,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-11,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	11,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	7,	jorg_step_left,
+	ai_move,	17,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	jorg_step_right
+};
+mmove_t jorg_move_pain3 = {FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run};
+
+mframe_t jorg_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_pain2 = {FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run};
+
+mframe_t jorg_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_pain1 = {FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run};
+
+mframe_t jorg_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 30
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 40
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	MakronToss,
+	ai_move,	0,	BossExplode		// 50
+};
+mmove_t jorg_move_death = {FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead};
+
+mframe_t jorg_frames_attack2 []=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	jorgBFG,		
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_attack2 = {FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run};
+
+mframe_t jorg_frames_start_attack1 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t jorg_move_start_attack1 = {FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1};
+
+mframe_t jorg_frames_attack1[]=
+{
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet
+};
+mmove_t jorg_move_attack1 = {FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1};
+
+mframe_t jorg_frames_end_attack1[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_end_attack1 = {FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run};
+
+void jorg_reattack1(edict_t *self)
+{
+	if (visible(self, self->enemy))
+		if (qrandom() < 0.9)
+			self->monsterinfo.currentmove = &jorg_move_attack1;
+		else
+		{
+			self->s.sound = 0;
+			self->monsterinfo.currentmove = &jorg_move_end_attack1;	
+		}
+	else
+	{
+		self->s.sound = 0;
+		self->monsterinfo.currentmove = &jorg_move_end_attack1;	
+	}
+}
+
+void jorg_attack1(edict_t *self)
+{
+	self->monsterinfo.currentmove = &jorg_move_attack1;
+}
+
+void jorg_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+	
+	self->s.sound = 0;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames if he takes little damage
+	if (damage <= 40)
+		if (qrandom()<=0.6)
+			return;
+
+	/* 
+	If he's entering his attack1 or using attack1, lessen the chance of him
+	going into pain
+	*/
+	
+	if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108) )
+		if (qrandom() <= 0.005)
+			return;
+
+	if ( (self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114) )
+		if (qrandom() <= 0.00005)
+			return;
+
+
+	if ( (self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208) )
+		if (qrandom() <= 0.005)
+			return;
+
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 50)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_pain1;
+	}
+	else if (damage <= 100)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_pain2;
+	}
+	else
+	{
+		if (qrandom() <= 0.3)
+		{
+			gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0);
+			self->monsterinfo.currentmove = &jorg_move_pain3;
+		}
+	}
+};
+
+void jorgBFG (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+	/*void monster_fire_bfg (edict_t *self, 
+							 vec3_t start, 
+							 vec3_t aimdir, 
+							 int damage, 
+							 int speed, 
+							 int kick, 
+							 float damage_radius, 
+							 int flashtype)*/
+	monster_fire_bfg (self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1);
+}	
+
+void jorg_firebullet_right (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1);
+}	
+
+void jorg_firebullet_left (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1);
+}	
+
+void jorg_firebullet (edict_t *self)
+{
+	jorg_firebullet_left(self);
+	jorg_firebullet_right(self);
+};
+
+void jorg_attack(edict_t *self)
+{
+	vec3_t	vec;
+	
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+
+	if (qrandom() <= 0.75)
+	{
+		gi.sound (self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM,0);
+		self->s.sound = gi.soundindex ("boss3/w_loop.wav");
+		self->monsterinfo.currentmove = &jorg_move_start_attack1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_attack2;
+	}
+}
+
+void jorg_dead (edict_t *)
+{
+/*
+	edict_t	*tempent;
+	//VectorSet (self->mins, -16, -16, -24);
+	//VectorSet (self->maxs, 16, 16, -8);
+	
+	// Jorg is on modelindex2. Do not clear him.
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->nextthink = 0;
+	gi.linkentity (self);
+
+	tempent = G_Spawn();
+	VectorCopy (self->s.origin, tempent->s.origin);
+	VectorCopy (self->s.angles, tempent->s.angles);
+	tempent->killtarget = self->killtarget;
+	tempent->target = self->target;
+	tempent->activator = self->enemy;
+	self->killtarget = 0;
+	self->target = 0;
+	SP_monster_makron (tempent);
+*/
+}
+
+
+void jorg_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->s.sound = 0;
+	self->count = 0;
+	self->monsterinfo.currentmove = &jorg_move_death;
+}
+
+qboolean Jorg_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.2;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+void MakronPrecache (void);
+
+/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_jorg (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("boss3/bs3pain1.wav");
+	sound_pain2 = gi.soundindex ("boss3/bs3pain2.wav");
+	sound_pain3 = gi.soundindex ("boss3/bs3pain3.wav");
+	sound_death = gi.soundindex ("boss3/bs3deth1.wav");
+	sound_attack1 = gi.soundindex ("boss3/bs3atck1.wav");
+	sound_attack2 = gi.soundindex ("boss3/bs3atck2.wav");
+	sound_search1 = gi.soundindex ("boss3/bs3srch1.wav");
+	sound_search2 = gi.soundindex ("boss3/bs3srch2.wav");
+	sound_search3 = gi.soundindex ("boss3/bs3srch3.wav");
+	sound_idle = gi.soundindex ("boss3/bs3idle1.wav");
+	sound_step_left = gi.soundindex ("boss3/step1.wav");
+	sound_step_right = gi.soundindex ("boss3/step2.wav");
+	sound_firegun = gi.soundindex ("boss3/xfire.wav");
+	sound_death_hit = gi.soundindex ("boss3/d_hit.wav");
+
+	MakronPrecache ();
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	self->s.modelindex2 = gi.modelindex ("models/monsters/boss3/jorg/tris.md2");
+	VectorSet (self->mins, -80, -80, 0);
+	VectorSet (self->maxs, 80, 80, 140);
+
+	self->health = 3000;
+	self->gib_health = -2000;
+	self->mass = 1000;
+
+	self->pain = jorg_pain;
+	self->die = jorg_die;
+	self->monsterinfo.stand = jorg_stand;
+	self->monsterinfo.walk = jorg_walk;
+	self->monsterinfo.run = jorg_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = jorg_attack;
+	self->monsterinfo.search = jorg_search;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+	self->monsterinfo.checkattack = Jorg_CheckAttack;
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &jorg_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+	//PMM
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+	//pmm
+
+}
--- /dev/null
+++ b/rogue/m_boss31.h
@@ -1,0 +1,194 @@
+// G:\quake2\baseq2\models/monsters/boss3/jorg
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak201        	18
+#define FRAME_attak202        	19
+#define FRAME_attak203        	20
+#define FRAME_attak204        	21
+#define FRAME_attak205        	22
+#define FRAME_attak206        	23
+#define FRAME_attak207        	24
+#define FRAME_attak208        	25
+#define FRAME_attak209        	26
+#define FRAME_attak210        	27
+#define FRAME_attak211        	28
+#define FRAME_attak212        	29
+#define FRAME_attak213        	30
+#define FRAME_death01         	31
+#define FRAME_death02         	32
+#define FRAME_death03         	33
+#define FRAME_death04         	34
+#define FRAME_death05         	35
+#define FRAME_death06         	36
+#define FRAME_death07         	37
+#define FRAME_death08         	38
+#define FRAME_death09         	39
+#define FRAME_death10         	40
+#define FRAME_death11         	41
+#define FRAME_death12         	42
+#define FRAME_death13         	43
+#define FRAME_death14         	44
+#define FRAME_death15         	45
+#define FRAME_death16         	46
+#define FRAME_death17         	47
+#define FRAME_death18         	48
+#define FRAME_death19         	49
+#define FRAME_death20         	50
+#define FRAME_death21         	51
+#define FRAME_death22         	52
+#define FRAME_death23         	53
+#define FRAME_death24         	54
+#define FRAME_death25         	55
+#define FRAME_death26         	56
+#define FRAME_death27         	57
+#define FRAME_death28         	58
+#define FRAME_death29         	59
+#define FRAME_death30         	60
+#define FRAME_death31         	61
+#define FRAME_death32         	62
+#define FRAME_death33         	63
+#define FRAME_death34         	64
+#define FRAME_death35         	65
+#define FRAME_death36         	66
+#define FRAME_death37         	67
+#define FRAME_death38         	68
+#define FRAME_death39         	69
+#define FRAME_death40         	70
+#define FRAME_death41         	71
+#define FRAME_death42         	72
+#define FRAME_death43         	73
+#define FRAME_death44         	74
+#define FRAME_death45         	75
+#define FRAME_death46         	76
+#define FRAME_death47         	77
+#define FRAME_death48         	78
+#define FRAME_death49         	79
+#define FRAME_death50         	80
+#define FRAME_pain101         	81
+#define FRAME_pain102         	82
+#define FRAME_pain103         	83
+#define FRAME_pain201         	84
+#define FRAME_pain202         	85
+#define FRAME_pain203         	86
+#define FRAME_pain301         	87
+#define FRAME_pain302         	88
+#define FRAME_pain303         	89
+#define FRAME_pain304         	90
+#define FRAME_pain305         	91
+#define FRAME_pain306         	92
+#define FRAME_pain307         	93
+#define FRAME_pain308         	94
+#define FRAME_pain309         	95
+#define FRAME_pain310         	96
+#define FRAME_pain311         	97
+#define FRAME_pain312         	98
+#define FRAME_pain313         	99
+#define FRAME_pain314         	100
+#define FRAME_pain315         	101
+#define FRAME_pain316         	102
+#define FRAME_pain317         	103
+#define FRAME_pain318         	104
+#define FRAME_pain319         	105
+#define FRAME_pain320         	106
+#define FRAME_pain321         	107
+#define FRAME_pain322         	108
+#define FRAME_pain323         	109
+#define FRAME_pain324         	110
+#define FRAME_pain325         	111
+#define FRAME_stand01         	112
+#define FRAME_stand02         	113
+#define FRAME_stand03         	114
+#define FRAME_stand04         	115
+#define FRAME_stand05         	116
+#define FRAME_stand06         	117
+#define FRAME_stand07         	118
+#define FRAME_stand08         	119
+#define FRAME_stand09         	120
+#define FRAME_stand10         	121
+#define FRAME_stand11         	122
+#define FRAME_stand12         	123
+#define FRAME_stand13         	124
+#define FRAME_stand14         	125
+#define FRAME_stand15         	126
+#define FRAME_stand16         	127
+#define FRAME_stand17         	128
+#define FRAME_stand18         	129
+#define FRAME_stand19         	130
+#define FRAME_stand20         	131
+#define FRAME_stand21         	132
+#define FRAME_stand22         	133
+#define FRAME_stand23         	134
+#define FRAME_stand24         	135
+#define FRAME_stand25         	136
+#define FRAME_stand26         	137
+#define FRAME_stand27         	138
+#define FRAME_stand28         	139
+#define FRAME_stand29         	140
+#define FRAME_stand30         	141
+#define FRAME_stand31         	142
+#define FRAME_stand32         	143
+#define FRAME_stand33         	144
+#define FRAME_stand34         	145
+#define FRAME_stand35         	146
+#define FRAME_stand36         	147
+#define FRAME_stand37         	148
+#define FRAME_stand38         	149
+#define FRAME_stand39         	150
+#define FRAME_stand40         	151
+#define FRAME_stand41         	152
+#define FRAME_stand42         	153
+#define FRAME_stand43         	154
+#define FRAME_stand44         	155
+#define FRAME_stand45         	156
+#define FRAME_stand46         	157
+#define FRAME_stand47         	158
+#define FRAME_stand48         	159
+#define FRAME_stand49         	160
+#define FRAME_stand50         	161
+#define FRAME_stand51         	162
+#define FRAME_walk01          	163
+#define FRAME_walk02          	164
+#define FRAME_walk03          	165
+#define FRAME_walk04          	166
+#define FRAME_walk05          	167
+#define FRAME_walk06          	168
+#define FRAME_walk07          	169
+#define FRAME_walk08          	170
+#define FRAME_walk09          	171
+#define FRAME_walk10          	172
+#define FRAME_walk11          	173
+#define FRAME_walk12          	174
+#define FRAME_walk13          	175
+#define FRAME_walk14          	176
+#define FRAME_walk15          	177
+#define FRAME_walk16          	178
+#define FRAME_walk17          	179
+#define FRAME_walk18          	180
+#define FRAME_walk19          	181
+#define FRAME_walk20          	182
+#define FRAME_walk21          	183
+#define FRAME_walk22          	184
+#define FRAME_walk23          	185
+#define FRAME_walk24          	186
+#define FRAME_walk25          	187
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_boss32.c
@@ -1,0 +1,890 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss32.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+void MakronRailgun (edict_t *self);
+void MakronSaveloc (edict_t *self);
+void MakronHyperblaster (edict_t *self);
+void makron_step_left (edict_t *self);
+void makron_step_right (edict_t *self);
+void makronBFG (edict_t *self);
+void makron_dead (edict_t *self);
+
+static int	sound_pain4;
+static int	sound_pain5;
+static int	sound_pain6;
+static int	sound_death;
+static int	sound_step_left;
+static int	sound_step_right;
+static int	sound_attack_bfg;
+static int	sound_brainsplorch;
+static int	sound_prerailgun;
+static int	sound_popup;
+static int	sound_taunt1;
+static int	sound_taunt2;
+static int	sound_taunt3;
+static int	sound_hit;
+
+void makron_taunt (edict_t *self)
+{
+	float r;
+
+	r = qrandom();
+	if (r <= 0.3)
+		gi.sound (self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0);
+	else if (r <= 0.6)
+		gi.sound (self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0);
+	else
+		gi.sound (self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0);
+}
+
+//
+// stand
+//
+
+mframe_t makron_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 40
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 50
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL		// 60
+};
+mmove_t	makron_move_stand = {FRAME_stand201, FRAME_stand260, makron_frames_stand, NULL};
+	
+void makron_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &makron_move_stand;
+}
+
+mframe_t makron_frames_run [] =
+{
+	ai_run, 3,	makron_step_left,
+	ai_run, 12,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 8,	makron_step_right,
+	ai_run, 6,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 6,	NULL,
+	ai_run, 12,	NULL
+};
+mmove_t	makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL};
+
+void makron_hit (edict_t *self)
+{
+	gi.sound (self, CHAN_AUTO, sound_hit, 1, ATTN_NONE,0);
+}
+
+void makron_popup (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_popup, 1, ATTN_NONE,0);
+}
+
+void makron_step_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0);
+}
+
+void makron_step_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0);
+}
+
+void makron_brainsplorch (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM,0);
+}
+
+void makron_prerailgun (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM,0);
+}
+
+
+mframe_t makron_frames_walk [] =
+{
+	ai_walk, 3,	makron_step_left,
+	ai_walk, 12,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 8,	makron_step_right,
+	ai_walk, 6,	NULL,
+	ai_walk, 12,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 6,	NULL,
+	ai_walk, 12,	NULL
+};
+mmove_t	makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL};
+
+void makron_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &makron_move_walk;
+}
+
+void makron_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &makron_move_stand;
+	else
+		self->monsterinfo.currentmove = &makron_move_run;
+}
+
+mframe_t makron_frames_pain6 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	makron_popup,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	makron_taunt,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain6 = {FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run};
+
+mframe_t makron_frames_pain5 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain5 = {FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run};
+
+mframe_t makron_frames_pain4 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain4 = {FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run};
+
+mframe_t makron_frames_death2 [] =
+{
+	ai_move,	-15,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	-12,	NULL,
+	ai_move,	0,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	11,	NULL,
+	ai_move,	12,	NULL,
+	ai_move,	11,	makron_step_right,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 30
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	6,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	2,	NULL,			// 40
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,			// 50
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-6,	makron_step_right,
+	ai_move,	-4,	NULL,
+	ai_move,	-4,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 60
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	-5,	NULL,
+	ai_move,	-3,	makron_step_right,
+	ai_move,	-8,	NULL,
+	ai_move,	-3,	makron_step_left,
+	ai_move,	-7,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-4,	makron_step_right,			// 70
+	ai_move,	-6,	NULL,			
+	ai_move,	-7,	NULL,
+	ai_move,	0,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 80
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,			// 90
+	ai_move,	27,	makron_hit,			
+	ai_move,	26,	NULL,
+	ai_move,	0,	makron_brainsplorch,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL			// 95
+};
+mmove_t makron_move_death2 = {FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead};
+
+mframe_t makron_frames_death3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_death3 = {FRAME_death301, FRAME_death320, makron_frames_death3, NULL};
+
+mframe_t makron_frames_sight [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run};
+
+void makronBFG (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	gi.sound (self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0);
+	monster_fire_bfg (self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG);
+}	
+
+
+mframe_t makron_frames_attack3 []=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	makronBFG,		// FIXME: BFG Attack here
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack3 = {FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run};
+
+mframe_t makron_frames_attack4[]=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack4 = {FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run};
+
+mframe_t makron_frames_attack5[]=
+{
+	ai_charge,	0,	makron_prerailgun,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	MakronSaveloc,
+	ai_move,	0,	MakronRailgun,		// Fire railgun
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack5 = {FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run};
+
+void MakronSaveloc (edict_t *self)
+{
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+};
+
+// FIXME: He's not firing from the proper Z
+void MakronRailgun (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right, start);
+	
+	// calc direction to where we targted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1);
+}
+
+// FIXME: This is all wrong. He's not firing at the proper angles.
+void MakronHyperblaster (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, vec);
+		vectoangles (vec, vec);
+		dir[0] = vec[0];
+	}
+	else
+	{
+		dir[0] = 0;
+	}
+	if (self->s.frame <= FRAME_attak413)
+		dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413);
+	else
+		dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421);
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, NULL, NULL);
+
+	monster_fire_blaster (self, start, forward, 15, 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER);
+}	
+
+
+void makron_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames
+	if (damage <=25)
+		if (qrandom()<0.2)
+			return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+
+	if (damage <= 40)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE,0);
+		self->monsterinfo.currentmove = &makron_move_pain4;
+	}
+	else if (damage <= 110)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE,0);
+		self->monsterinfo.currentmove = &makron_move_pain5;
+	}
+	else
+	{
+		if (damage <= 150)
+			if (qrandom() <= 0.45)
+			{
+				gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0);
+				self->monsterinfo.currentmove = &makron_move_pain6;
+			}
+		else
+			if (qrandom() <= 0.35)
+			{
+				gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0);
+				self->monsterinfo.currentmove = &makron_move_pain6;
+			}
+	}
+};
+
+void makron_sight(edict_t *self, edict_t *)
+{
+	self->monsterinfo.currentmove = &makron_move_sight;
+};
+
+void makron_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	r;
+
+	r = qrandom();
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+
+
+	if (r <= 0.3)
+		self->monsterinfo.currentmove = &makron_move_attack3;
+	else if (r <= 0.6)
+		self->monsterinfo.currentmove = &makron_move_attack4;
+	else
+		self->monsterinfo.currentmove = &makron_move_attack5;
+}
+
+/*
+---
+Makron Torso. This needs to be spawned in
+---
+*/
+
+void makron_torso_think (edict_t *self)
+{
+	if (++self->s.frame < 365)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 346;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void makron_torso (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	VectorSet (ent->mins, -8, -8, 0);
+	VectorSet (ent->maxs, 8, 8, 8);
+	ent->s.frame = 346;
+	ent->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	ent->think = makron_torso_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	ent->s.sound = gi.soundindex ("makron/spine.wav");
+	gi.linkentity (ent);
+}
+
+
+//
+// death
+//
+
+void makron_dead (edict_t *self)
+{
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void makron_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	edict_t *tempent;
+
+	int		n;
+
+	self->s.sound = 0;
+	// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 1 /*4*/; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	tempent = G_Spawn();
+	VectorCopy (self->s.origin, tempent->s.origin);
+	VectorCopy (self->s.angles, tempent->s.angles);
+	tempent->s.origin[1] -= 84;
+	makron_torso (tempent);
+
+	self->monsterinfo.currentmove = &makron_move_death2;
+	
+}
+
+qboolean Makron_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.2;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+//
+// monster_makron
+//
+
+void MakronPrecache (void)
+{
+	sound_pain4 = gi.soundindex ("makron/pain3.wav");
+	sound_pain5 = gi.soundindex ("makron/pain2.wav");
+	sound_pain6 = gi.soundindex ("makron/pain1.wav");
+	sound_death = gi.soundindex ("makron/death.wav");
+	sound_step_left = gi.soundindex ("makron/step1.wav");
+	sound_step_right = gi.soundindex ("makron/step2.wav");
+	sound_attack_bfg = gi.soundindex ("makron/bfg_fire.wav");
+	sound_brainsplorch = gi.soundindex ("makron/brain1.wav");
+	sound_prerailgun = gi.soundindex ("makron/rail_up.wav");
+	sound_popup = gi.soundindex ("makron/popup.wav");
+	sound_taunt1 = gi.soundindex ("makron/voice4.wav");
+	sound_taunt2 = gi.soundindex ("makron/voice3.wav");
+	sound_taunt3 = gi.soundindex ("makron/voice.wav");
+	sound_hit = gi.soundindex ("makron/bhit.wav");
+
+	gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+}
+
+/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_makron (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	MakronPrecache ();
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	VectorSet (self->mins, -30, -30, 0);
+	VectorSet (self->maxs, 30, 30, 90);
+
+	self->health = 3000;
+	self->gib_health = -2000;
+	self->mass = 500;
+
+	self->pain = makron_pain;
+	self->die = makron_die;
+	self->monsterinfo.stand = makron_stand;
+	self->monsterinfo.walk = makron_walk;
+	self->monsterinfo.run = makron_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = makron_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = makron_sight;
+	self->monsterinfo.checkattack = Makron_CheckAttack;
+
+	gi.linkentity (self);
+	
+//	self->monsterinfo.currentmove = &makron_move_stand;
+	self->monsterinfo.currentmove = &makron_move_sight;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+
+	//PMM
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+	//pmm
+}
+
+
+/*
+=================
+MakronSpawn
+
+=================
+*/
+void MakronSpawn (edict_t *self)
+{
+	vec3_t		vec;
+	edict_t		*player;
+
+	SP_monster_makron (self);
+
+	// jump at player
+	player = level.sight_client;
+	if (!player)
+		return;
+
+	VectorSubtract (player->s.origin, self->s.origin, vec);
+	self->s.angles[YAW] = vectoyaw(vec);
+	VectorNormalize (vec);
+	VectorMA (vec3_origin, 400, vec, self->velocity);
+	self->velocity[2] = 200;
+	self->groundentity = NULL;
+}
+
+/*
+=================
+MakronToss
+
+Jorg is just about dead, so set up to launch Makron out
+=================
+*/
+void MakronToss (edict_t *self)
+{
+	edict_t	*ent;
+
+	ent = G_Spawn ();
+	ent->nextthink = level.time + 0.8;
+	ent->think = MakronSpawn;
+	ent->target = self->target;
+	VectorCopy (self->s.origin, ent->s.origin);
+}
--- /dev/null
+++ b/rogue/m_boss32.h
@@ -1,0 +1,497 @@
+// G:\quake2\baseq2\models/monsters/boss3/rider
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak201        	18
+#define FRAME_attak202        	19
+#define FRAME_attak203        	20
+#define FRAME_attak204        	21
+#define FRAME_attak205        	22
+#define FRAME_attak206        	23
+#define FRAME_attak207        	24
+#define FRAME_attak208        	25
+#define FRAME_attak209        	26
+#define FRAME_attak210        	27
+#define FRAME_attak211        	28
+#define FRAME_attak212        	29
+#define FRAME_attak213        	30
+#define FRAME_death01         	31
+#define FRAME_death02         	32
+#define FRAME_death03         	33
+#define FRAME_death04         	34
+#define FRAME_death05         	35
+#define FRAME_death06         	36
+#define FRAME_death07         	37
+#define FRAME_death08         	38
+#define FRAME_death09         	39
+#define FRAME_death10         	40
+#define FRAME_death11         	41
+#define FRAME_death12         	42
+#define FRAME_death13         	43
+#define FRAME_death14         	44
+#define FRAME_death15         	45
+#define FRAME_death16         	46
+#define FRAME_death17         	47
+#define FRAME_death18         	48
+#define FRAME_death19         	49
+#define FRAME_death20         	50
+#define FRAME_death21         	51
+#define FRAME_death22         	52
+#define FRAME_death23         	53
+#define FRAME_death24         	54
+#define FRAME_death25         	55
+#define FRAME_death26         	56
+#define FRAME_death27         	57
+#define FRAME_death28         	58
+#define FRAME_death29         	59
+#define FRAME_death30         	60
+#define FRAME_death31         	61
+#define FRAME_death32         	62
+#define FRAME_death33         	63
+#define FRAME_death34         	64
+#define FRAME_death35         	65
+#define FRAME_death36         	66
+#define FRAME_death37         	67
+#define FRAME_death38         	68
+#define FRAME_death39         	69
+#define FRAME_death40         	70
+#define FRAME_death41         	71
+#define FRAME_death42         	72
+#define FRAME_death43         	73
+#define FRAME_death44         	74
+#define FRAME_death45         	75
+#define FRAME_death46         	76
+#define FRAME_death47         	77
+#define FRAME_death48         	78
+#define FRAME_death49         	79
+#define FRAME_death50         	80
+#define FRAME_pain101         	81
+#define FRAME_pain102         	82
+#define FRAME_pain103         	83
+#define FRAME_pain201         	84
+#define FRAME_pain202         	85
+#define FRAME_pain203         	86
+#define FRAME_pain301         	87
+#define FRAME_pain302         	88
+#define FRAME_pain303         	89
+#define FRAME_pain304         	90
+#define FRAME_pain305         	91
+#define FRAME_pain306         	92
+#define FRAME_pain307         	93
+#define FRAME_pain308         	94
+#define FRAME_pain309         	95
+#define FRAME_pain310         	96
+#define FRAME_pain311         	97
+#define FRAME_pain312         	98
+#define FRAME_pain313         	99
+#define FRAME_pain314         	100
+#define FRAME_pain315         	101
+#define FRAME_pain316         	102
+#define FRAME_pain317         	103
+#define FRAME_pain318         	104
+#define FRAME_pain319         	105
+#define FRAME_pain320         	106
+#define FRAME_pain321         	107
+#define FRAME_pain322         	108
+#define FRAME_pain323         	109
+#define FRAME_pain324         	110
+#define FRAME_pain325         	111
+#define FRAME_stand01         	112
+#define FRAME_stand02         	113
+#define FRAME_stand03         	114
+#define FRAME_stand04         	115
+#define FRAME_stand05         	116
+#define FRAME_stand06         	117
+#define FRAME_stand07         	118
+#define FRAME_stand08         	119
+#define FRAME_stand09         	120
+#define FRAME_stand10         	121
+#define FRAME_stand11         	122
+#define FRAME_stand12         	123
+#define FRAME_stand13         	124
+#define FRAME_stand14         	125
+#define FRAME_stand15         	126
+#define FRAME_stand16         	127
+#define FRAME_stand17         	128
+#define FRAME_stand18         	129
+#define FRAME_stand19         	130
+#define FRAME_stand20         	131
+#define FRAME_stand21         	132
+#define FRAME_stand22         	133
+#define FRAME_stand23         	134
+#define FRAME_stand24         	135
+#define FRAME_stand25         	136
+#define FRAME_stand26         	137
+#define FRAME_stand27         	138
+#define FRAME_stand28         	139
+#define FRAME_stand29         	140
+#define FRAME_stand30         	141
+#define FRAME_stand31         	142
+#define FRAME_stand32         	143
+#define FRAME_stand33         	144
+#define FRAME_stand34         	145
+#define FRAME_stand35         	146
+#define FRAME_stand36         	147
+#define FRAME_stand37         	148
+#define FRAME_stand38         	149
+#define FRAME_stand39         	150
+#define FRAME_stand40         	151
+#define FRAME_stand41         	152
+#define FRAME_stand42         	153
+#define FRAME_stand43         	154
+#define FRAME_stand44         	155
+#define FRAME_stand45         	156
+#define FRAME_stand46         	157
+#define FRAME_stand47         	158
+#define FRAME_stand48         	159
+#define FRAME_stand49         	160
+#define FRAME_stand50         	161
+#define FRAME_stand51         	162
+#define FRAME_walk01          	163
+#define FRAME_walk02          	164
+#define FRAME_walk03          	165
+#define FRAME_walk04          	166
+#define FRAME_walk05          	167
+#define FRAME_walk06          	168
+#define FRAME_walk07          	169
+#define FRAME_walk08          	170
+#define FRAME_walk09          	171
+#define FRAME_walk10          	172
+#define FRAME_walk11          	173
+#define FRAME_walk12          	174
+#define FRAME_walk13          	175
+#define FRAME_walk14          	176
+#define FRAME_walk15          	177
+#define FRAME_walk16          	178
+#define FRAME_walk17          	179
+#define FRAME_walk18          	180
+#define FRAME_walk19          	181
+#define FRAME_walk20          	182
+#define FRAME_walk21          	183
+#define FRAME_walk22          	184
+#define FRAME_walk23          	185
+#define FRAME_walk24          	186
+#define FRAME_walk25          	187
+#define FRAME_active01        	188
+#define FRAME_active02        	189
+#define FRAME_active03        	190
+#define FRAME_active04        	191
+#define FRAME_active05        	192
+#define FRAME_active06        	193
+#define FRAME_active07        	194
+#define FRAME_active08        	195
+#define FRAME_active09        	196
+#define FRAME_active10        	197
+#define FRAME_active11        	198
+#define FRAME_active12        	199
+#define FRAME_active13        	200
+#define FRAME_attak301        	201
+#define FRAME_attak302        	202
+#define FRAME_attak303        	203
+#define FRAME_attak304        	204
+#define FRAME_attak305        	205
+#define FRAME_attak306        	206
+#define FRAME_attak307        	207
+#define FRAME_attak308        	208
+#define FRAME_attak401        	209
+#define FRAME_attak402        	210
+#define FRAME_attak403        	211
+#define FRAME_attak404        	212
+#define FRAME_attak405        	213
+#define FRAME_attak406        	214
+#define FRAME_attak407        	215
+#define FRAME_attak408        	216
+#define FRAME_attak409        	217
+#define FRAME_attak410        	218
+#define FRAME_attak411        	219
+#define FRAME_attak412        	220
+#define FRAME_attak413        	221
+#define FRAME_attak414        	222
+#define FRAME_attak415        	223
+#define FRAME_attak416        	224
+#define FRAME_attak417        	225
+#define FRAME_attak418        	226
+#define FRAME_attak419        	227
+#define FRAME_attak420        	228
+#define FRAME_attak421        	229
+#define FRAME_attak422        	230
+#define FRAME_attak423        	231
+#define FRAME_attak424        	232
+#define FRAME_attak425        	233
+#define FRAME_attak426        	234
+#define FRAME_attak501        	235
+#define FRAME_attak502        	236
+#define FRAME_attak503        	237
+#define FRAME_attak504        	238
+#define FRAME_attak505        	239
+#define FRAME_attak506        	240
+#define FRAME_attak507        	241
+#define FRAME_attak508        	242
+#define FRAME_attak509        	243
+#define FRAME_attak510        	244
+#define FRAME_attak511        	245
+#define FRAME_attak512        	246
+#define FRAME_attak513        	247
+#define FRAME_attak514        	248
+#define FRAME_attak515        	249
+#define FRAME_attak516        	250
+#define FRAME_death201        	251
+#define FRAME_death202        	252
+#define FRAME_death203        	253
+#define FRAME_death204        	254
+#define FRAME_death205        	255
+#define FRAME_death206        	256
+#define FRAME_death207        	257
+#define FRAME_death208        	258
+#define FRAME_death209        	259
+#define FRAME_death210        	260
+#define FRAME_death211        	261
+#define FRAME_death212        	262
+#define FRAME_death213        	263
+#define FRAME_death214        	264
+#define FRAME_death215        	265
+#define FRAME_death216        	266
+#define FRAME_death217        	267
+#define FRAME_death218        	268
+#define FRAME_death219        	269
+#define FRAME_death220        	270
+#define FRAME_death221        	271
+#define FRAME_death222        	272
+#define FRAME_death223        	273
+#define FRAME_death224        	274
+#define FRAME_death225        	275
+#define FRAME_death226        	276
+#define FRAME_death227        	277
+#define FRAME_death228        	278
+#define FRAME_death229        	279
+#define FRAME_death230        	280
+#define FRAME_death231        	281
+#define FRAME_death232        	282
+#define FRAME_death233        	283
+#define FRAME_death234        	284
+#define FRAME_death235        	285
+#define FRAME_death236        	286
+#define FRAME_death237        	287
+#define FRAME_death238        	288
+#define FRAME_death239        	289
+#define FRAME_death240        	290
+#define FRAME_death241        	291
+#define FRAME_death242        	292
+#define FRAME_death243        	293
+#define FRAME_death244        	294
+#define FRAME_death245        	295
+#define FRAME_death246        	296
+#define FRAME_death247        	297
+#define FRAME_death248        	298
+#define FRAME_death249        	299
+#define FRAME_death250        	300
+#define FRAME_death251        	301
+#define FRAME_death252        	302
+#define FRAME_death253        	303
+#define FRAME_death254        	304
+#define FRAME_death255        	305
+#define FRAME_death256        	306
+#define FRAME_death257        	307
+#define FRAME_death258        	308
+#define FRAME_death259        	309
+#define FRAME_death260        	310
+#define FRAME_death261        	311
+#define FRAME_death262        	312
+#define FRAME_death263        	313
+#define FRAME_death264        	314
+#define FRAME_death265        	315
+#define FRAME_death266        	316
+#define FRAME_death267        	317
+#define FRAME_death268        	318
+#define FRAME_death269        	319
+#define FRAME_death270        	320
+#define FRAME_death271        	321
+#define FRAME_death272        	322
+#define FRAME_death273        	323
+#define FRAME_death274        	324
+#define FRAME_death275        	325
+#define FRAME_death276        	326
+#define FRAME_death277        	327
+#define FRAME_death278        	328
+#define FRAME_death279        	329
+#define FRAME_death280        	330
+#define FRAME_death281        	331
+#define FRAME_death282        	332
+#define FRAME_death283        	333
+#define FRAME_death284        	334
+#define FRAME_death285        	335
+#define FRAME_death286        	336
+#define FRAME_death287        	337
+#define FRAME_death288        	338
+#define FRAME_death289        	339
+#define FRAME_death290        	340
+#define FRAME_death291        	341
+#define FRAME_death292        	342
+#define FRAME_death293        	343
+#define FRAME_death294        	344
+#define FRAME_death295        	345
+#define FRAME_death301        	346
+#define FRAME_death302        	347
+#define FRAME_death303        	348
+#define FRAME_death304        	349
+#define FRAME_death305        	350
+#define FRAME_death306        	351
+#define FRAME_death307        	352
+#define FRAME_death308        	353
+#define FRAME_death309        	354
+#define FRAME_death310        	355
+#define FRAME_death311        	356
+#define FRAME_death312        	357
+#define FRAME_death313        	358
+#define FRAME_death314        	359
+#define FRAME_death315        	360
+#define FRAME_death316        	361
+#define FRAME_death317        	362
+#define FRAME_death318        	363
+#define FRAME_death319        	364
+#define FRAME_death320        	365
+#define FRAME_jump01          	366
+#define FRAME_jump02          	367
+#define FRAME_jump03          	368
+#define FRAME_jump04          	369
+#define FRAME_jump05          	370
+#define FRAME_jump06          	371
+#define FRAME_jump07          	372
+#define FRAME_jump08          	373
+#define FRAME_jump09          	374
+#define FRAME_jump10          	375
+#define FRAME_jump11          	376
+#define FRAME_jump12          	377
+#define FRAME_jump13          	378
+#define FRAME_pain401         	379
+#define FRAME_pain402         	380
+#define FRAME_pain403         	381
+#define FRAME_pain404         	382
+#define FRAME_pain501         	383
+#define FRAME_pain502         	384
+#define FRAME_pain503         	385
+#define FRAME_pain504         	386
+#define FRAME_pain601         	387
+#define FRAME_pain602         	388
+#define FRAME_pain603         	389
+#define FRAME_pain604         	390
+#define FRAME_pain605         	391
+#define FRAME_pain606         	392
+#define FRAME_pain607         	393
+#define FRAME_pain608         	394
+#define FRAME_pain609         	395
+#define FRAME_pain610         	396
+#define FRAME_pain611         	397
+#define FRAME_pain612         	398
+#define FRAME_pain613         	399
+#define FRAME_pain614         	400
+#define FRAME_pain615         	401
+#define FRAME_pain616         	402
+#define FRAME_pain617         	403
+#define FRAME_pain618         	404
+#define FRAME_pain619         	405
+#define FRAME_pain620         	406
+#define FRAME_pain621         	407
+#define FRAME_pain622         	408
+#define FRAME_pain623         	409
+#define FRAME_pain624         	410
+#define FRAME_pain625         	411
+#define FRAME_pain626         	412
+#define FRAME_pain627         	413
+#define FRAME_stand201        	414
+#define FRAME_stand202        	415
+#define FRAME_stand203        	416
+#define FRAME_stand204        	417
+#define FRAME_stand205        	418
+#define FRAME_stand206        	419
+#define FRAME_stand207        	420
+#define FRAME_stand208        	421
+#define FRAME_stand209        	422
+#define FRAME_stand210        	423
+#define FRAME_stand211        	424
+#define FRAME_stand212        	425
+#define FRAME_stand213        	426
+#define FRAME_stand214        	427
+#define FRAME_stand215        	428
+#define FRAME_stand216        	429
+#define FRAME_stand217        	430
+#define FRAME_stand218        	431
+#define FRAME_stand219        	432
+#define FRAME_stand220        	433
+#define FRAME_stand221        	434
+#define FRAME_stand222        	435
+#define FRAME_stand223        	436
+#define FRAME_stand224        	437
+#define FRAME_stand225        	438
+#define FRAME_stand226        	439
+#define FRAME_stand227        	440
+#define FRAME_stand228        	441
+#define FRAME_stand229        	442
+#define FRAME_stand230        	443
+#define FRAME_stand231        	444
+#define FRAME_stand232        	445
+#define FRAME_stand233        	446
+#define FRAME_stand234        	447
+#define FRAME_stand235        	448
+#define FRAME_stand236        	449
+#define FRAME_stand237        	450
+#define FRAME_stand238        	451
+#define FRAME_stand239        	452
+#define FRAME_stand240        	453
+#define FRAME_stand241        	454
+#define FRAME_stand242        	455
+#define FRAME_stand243        	456
+#define FRAME_stand244        	457
+#define FRAME_stand245        	458
+#define FRAME_stand246        	459
+#define FRAME_stand247        	460
+#define FRAME_stand248        	461
+#define FRAME_stand249        	462
+#define FRAME_stand250        	463
+#define FRAME_stand251        	464
+#define FRAME_stand252        	465
+#define FRAME_stand253        	466
+#define FRAME_stand254        	467
+#define FRAME_stand255        	468
+#define FRAME_stand256        	469
+#define FRAME_stand257        	470
+#define FRAME_stand258        	471
+#define FRAME_stand259        	472
+#define FRAME_stand260        	473
+#define FRAME_walk201         	474
+#define FRAME_walk202         	475
+#define FRAME_walk203         	476
+#define FRAME_walk204         	477
+#define FRAME_walk205         	478
+#define FRAME_walk206         	479
+#define FRAME_walk207         	480
+#define FRAME_walk208         	481
+#define FRAME_walk209         	482
+#define FRAME_walk210         	483
+#define FRAME_walk211         	484
+#define FRAME_walk212         	485
+#define FRAME_walk213         	486
+#define FRAME_walk214         	487
+#define FRAME_walk215         	488
+#define FRAME_walk216         	489
+#define FRAME_walk217         	490
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_brain.c
@@ -1,0 +1,697 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_brain.h"
+
+
+static int	sound_chest_open;
+static int	sound_tentacles_extend;
+static int	sound_tentacles_retract;
+static int	sound_death;
+static int	sound_idle1;
+static int	sound_idle2;
+static int	sound_idle3;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_melee1;
+static int	sound_melee2;
+static int	sound_melee3;
+
+
+void brain_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void brain_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+void brain_run (edict_t *self);
+void brain_dead (edict_t *self);
+
+
+//
+// STAND
+//
+
+mframe_t brain_frames_stand [] =
+{
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL
+};
+mmove_t brain_move_stand = {FRAME_stand01, FRAME_stand30, brain_frames_stand, NULL};
+
+void brain_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &brain_move_stand;
+}
+
+
+//
+// IDLE
+//
+
+mframe_t brain_frames_idle [] =
+{
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL
+};
+mmove_t brain_move_idle = {FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand};
+
+void brain_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0);
+	self->monsterinfo.currentmove = &brain_move_idle;
+}
+
+
+//
+// WALK
+//
+mframe_t brain_frames_walk1 [] =
+{
+	ai_walk,	7,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	9,	NULL,
+	ai_walk,	-4,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	2,	NULL
+};
+mmove_t brain_move_walk1 = {FRAME_walk101, FRAME_walk111, brain_frames_walk1, NULL};
+
+// walk2 is FUBAR, do not use
+/*
+void brain_walk2_cycle (edict_t *self)
+{
+	if (qrandom() > 0.1)
+		self->monsterinfo.nextframe = FRAME_walk220;
+}
+
+mframe_t brain_frames_walk2 [] =
+{
+	ai_walk,	3,	NULL,
+	ai_walk,	-2,	NULL,
+	ai_walk,	-4,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	12,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	0,	NULL,
+
+	ai_walk,	-2,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	10,	NULL,		// Cycle Start
+
+	ai_walk,	-1,	NULL,
+	ai_walk,	7,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	0,	NULL,
+
+	ai_walk,	4,	brain_walk2_cycle,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-8,	NULL,		
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	5,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-5,	NULL
+};
+mmove_t brain_move_walk2 = {FRAME_walk201, FRAME_walk240, brain_frames_walk2, NULL};
+*/
+
+void brain_walk (edict_t *self)
+{
+//	if (random() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_walk1;
+//	else
+//		self->monsterinfo.currentmove = &brain_move_walk2;
+}
+
+
+
+mframe_t brain_frames_defense [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_defense = {FRAME_defens01, FRAME_defens08, brain_frames_defense, NULL};
+
+mframe_t brain_frames_pain3 [] =
+{
+	ai_move,	-2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-4,	NULL
+};
+mmove_t brain_move_pain3 = {FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run};
+
+mframe_t brain_frames_pain2 [] =
+{
+	ai_move,	-2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-2,	NULL
+};
+mmove_t brain_move_pain2 = {FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run};
+
+mframe_t brain_frames_pain1 [] =
+{
+	ai_move,	-6,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	-1,	NULL
+};
+mmove_t brain_move_pain1 = {FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run};
+
+mframe_t brain_frames_duck [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	-2,	monster_duck_down,
+	ai_move,	17,	monster_duck_hold,
+	ai_move,	-3,	NULL,
+	ai_move,	-1,	monster_duck_up,
+	ai_move,	-5,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-6,	NULL
+};
+mmove_t brain_move_duck = {FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run};
+
+/*
+void brain_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+	//========
+	//PMM - new dodge code
+	float	r;
+	float	height;
+
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	r = qrandom();
+	if (r > (0.25*((skill->value)+1)))
+		return;
+
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		height = self->absmax[2];
+	}
+	else
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+	}
+
+	// check to see if it makes sense to duck
+	if (tr->endpos[2] <= height)
+	{
+		// if it doesn't sense to duck, try to strafe and shoot
+		// FIXME - this guy is so slow, it's not worth it
+
+		//vec3_t forward,right,up,diff;
+
+		monster_done_dodge (self);
+		return;
+	}
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &brain_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.aiflags |= AI_DODGING;
+		return;
+	}
+
+	self->monsterinfo.currentmove = &brain_move_duck;
+	self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+	self->monsterinfo.aiflags |= AI_DODGING;
+	return;
+	//============
+	//PMM
+}
+*/
+
+mframe_t brain_frames_death2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	9,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_death2 = {FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead};
+
+mframe_t brain_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	9,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_death1 = {FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead};
+
+
+//
+// MELEE
+//
+
+void brain_swing_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0);
+}
+
+void brain_hit_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 40))
+		gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+}
+
+void brain_swing_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0);
+}
+
+void brain_hit_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 40))
+		gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+}
+
+mframe_t brain_frames_attack1 [] =
+{
+	ai_charge,	8,	NULL,
+	ai_charge,	3,	NULL,
+	ai_charge,	5,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	-3,	brain_swing_right,
+	ai_charge,	0,	NULL,
+	ai_charge,	-5,	NULL,
+	ai_charge,	-7,	brain_hit_right,
+	ai_charge,	0,	NULL,
+	ai_charge,	6,	brain_swing_left,
+	ai_charge,	1,	NULL,
+	ai_charge,	2,	brain_hit_left,
+	ai_charge,	-3,	NULL,
+	ai_charge,	6,	NULL,
+	ai_charge,	-1,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	-11,NULL
+};
+mmove_t brain_move_attack1 = {FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run};
+
+void brain_chest_open (edict_t *self)
+{
+	self->spawnflags &= ~65536;
+	self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+	gi.sound (self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
+}
+
+void brain_tentacle_attack (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), -600) && skill->value > 0)
+		self->spawnflags |= 65536;
+	gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+}
+
+void brain_chest_closed (edict_t *self)
+{
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	if (self->spawnflags & 65536)
+	{
+		self->spawnflags &= ~65536;
+		self->monsterinfo.currentmove = &brain_move_attack1;
+	}
+}
+
+mframe_t brain_frames_attack2 [] =
+{
+	ai_charge,	5,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	0,	brain_chest_open,
+	ai_charge,	0,	NULL,
+	ai_charge,	13,	brain_tentacle_attack,
+	ai_charge,	0,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	-9,	brain_chest_closed,
+	ai_charge,	0,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	3,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	-6,	NULL
+};
+mmove_t brain_move_attack2 = {FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run};
+
+void brain_melee(edict_t *self)
+{
+	if (qrandom() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_attack1;
+	else
+		self->monsterinfo.currentmove = &brain_move_attack2;
+}
+
+
+//
+// RUN
+//
+
+mframe_t brain_frames_run [] =
+{
+	ai_run,	9,	NULL,
+	ai_run,	2,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	1,	NULL,
+	ai_run,	0,	NULL,
+	ai_run,	0,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	-4,	NULL,
+	ai_run,	-1,	NULL,
+	ai_run,	2,	NULL
+};
+mmove_t brain_move_run = {FRAME_walk101, FRAME_walk111, brain_frames_run, NULL};
+
+void brain_run (edict_t *self)
+{
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &brain_move_stand;
+	else
+		self->monsterinfo.currentmove = &brain_move_run;
+}
+
+
+void brain_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+	if (r < 0.33)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain1;
+	}
+	else if (r < 0.66)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain3;
+	}
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+}
+
+void brain_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+
+void brain_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	self->s.effects = 0;
+	self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	if (qrandom() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_death1;
+	else
+		self->monsterinfo.currentmove = &brain_move_death2;
+}
+
+void brain_duck (edict_t *self, float eta)
+{
+	// has to be done immediately otherwise he can get stuck
+	monster_duck_down(self);
+
+	if (skill->value == 0)
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	else
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+
+	self->monsterinfo.currentmove = &brain_move_duck;
+	self->monsterinfo.nextframe = FRAME_duck01;
+	return;
+}
+
+
+/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_brain (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_chest_open = gi.soundindex ("brain/brnatck1.wav");
+	sound_tentacles_extend = gi.soundindex ("brain/brnatck2.wav");
+	sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav");
+	sound_death = gi.soundindex ("brain/brndeth1.wav");
+	sound_idle1 = gi.soundindex ("brain/brnidle1.wav");
+	sound_idle2 = gi.soundindex ("brain/brnidle2.wav");
+	sound_idle3 = gi.soundindex ("brain/brnlens1.wav");
+	sound_pain1 = gi.soundindex ("brain/brnpain1.wav");
+	sound_pain2 = gi.soundindex ("brain/brnpain2.wav");
+	sound_sight = gi.soundindex ("brain/brnsght1.wav");
+	sound_search = gi.soundindex ("brain/brnsrch1.wav");
+	sound_melee1 = gi.soundindex ("brain/melee1.wav");
+	sound_melee2 = gi.soundindex ("brain/melee2.wav");
+	sound_melee3 = gi.soundindex ("brain/melee3.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/brain/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 300;
+	self->gib_health = -150;
+	self->mass = 400;
+
+	self->pain = brain_pain;
+	self->die = brain_die;
+
+	self->monsterinfo.stand = brain_stand;
+	self->monsterinfo.walk = brain_walk;
+	self->monsterinfo.run = brain_run;
+// PMM
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.duck = brain_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+//	self->monsterinfo.dodge = brain_dodge;
+// pmm
+//	self->monsterinfo.attack = brain_attack;
+	self->monsterinfo.melee = brain_melee;
+	self->monsterinfo.sight = brain_sight;
+	self->monsterinfo.search = brain_search;
+	self->monsterinfo.idle = brain_idle;
+
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	self->monsterinfo.power_armor_power = 100;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &brain_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_brain.h
@@ -1,0 +1,228 @@
+// G:\quake2\baseq2\models/monsters/brain
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_walk101         	0
+#define FRAME_walk102         	1
+#define FRAME_walk103         	2
+#define FRAME_walk104         	3
+#define FRAME_walk105         	4
+#define FRAME_walk106         	5
+#define FRAME_walk107         	6
+#define FRAME_walk108         	7
+#define FRAME_walk109         	8
+#define FRAME_walk110         	9
+#define FRAME_walk111         	10
+#define FRAME_walk112         	11
+#define FRAME_walk113         	12
+#define FRAME_walk201         	13
+#define FRAME_walk202         	14
+#define FRAME_walk203         	15
+#define FRAME_walk204         	16
+#define FRAME_walk205         	17
+#define FRAME_walk206         	18
+#define FRAME_walk207         	19
+#define FRAME_walk208         	20
+#define FRAME_walk209         	21
+#define FRAME_walk210         	22
+#define FRAME_walk211         	23
+#define FRAME_walk212         	24
+#define FRAME_walk213         	25
+#define FRAME_walk214         	26
+#define FRAME_walk215         	27
+#define FRAME_walk216         	28
+#define FRAME_walk217         	29
+#define FRAME_walk218         	30
+#define FRAME_walk219         	31
+#define FRAME_walk220         	32
+#define FRAME_walk221         	33
+#define FRAME_walk222         	34
+#define FRAME_walk223         	35
+#define FRAME_walk224         	36
+#define FRAME_walk225         	37
+#define FRAME_walk226         	38
+#define FRAME_walk227         	39
+#define FRAME_walk228         	40
+#define FRAME_walk229         	41
+#define FRAME_walk230         	42
+#define FRAME_walk231         	43
+#define FRAME_walk232         	44
+#define FRAME_walk233         	45
+#define FRAME_walk234         	46
+#define FRAME_walk235         	47
+#define FRAME_walk236         	48
+#define FRAME_walk237         	49
+#define FRAME_walk238         	50
+#define FRAME_walk239         	51
+#define FRAME_walk240         	52
+#define FRAME_attak101        	53
+#define FRAME_attak102        	54
+#define FRAME_attak103        	55
+#define FRAME_attak104        	56
+#define FRAME_attak105        	57
+#define FRAME_attak106        	58
+#define FRAME_attak107        	59
+#define FRAME_attak108        	60
+#define FRAME_attak109        	61
+#define FRAME_attak110        	62
+#define FRAME_attak111        	63
+#define FRAME_attak112        	64
+#define FRAME_attak113        	65
+#define FRAME_attak114        	66
+#define FRAME_attak115        	67
+#define FRAME_attak116        	68
+#define FRAME_attak117        	69
+#define FRAME_attak118        	70
+#define FRAME_attak201        	71
+#define FRAME_attak202        	72
+#define FRAME_attak203        	73
+#define FRAME_attak204        	74
+#define FRAME_attak205        	75
+#define FRAME_attak206        	76
+#define FRAME_attak207        	77
+#define FRAME_attak208        	78
+#define FRAME_attak209        	79
+#define FRAME_attak210        	80
+#define FRAME_attak211        	81
+#define FRAME_attak212        	82
+#define FRAME_attak213        	83
+#define FRAME_attak214        	84
+#define FRAME_attak215        	85
+#define FRAME_attak216        	86
+#define FRAME_attak217        	87
+#define FRAME_pain101         	88
+#define FRAME_pain102         	89
+#define FRAME_pain103         	90
+#define FRAME_pain104         	91
+#define FRAME_pain105         	92
+#define FRAME_pain106         	93
+#define FRAME_pain107         	94
+#define FRAME_pain108         	95
+#define FRAME_pain109         	96
+#define FRAME_pain110         	97
+#define FRAME_pain111         	98
+#define FRAME_pain112         	99
+#define FRAME_pain113         	100
+#define FRAME_pain114         	101
+#define FRAME_pain115         	102
+#define FRAME_pain116         	103
+#define FRAME_pain117         	104
+#define FRAME_pain118         	105
+#define FRAME_pain119         	106
+#define FRAME_pain120         	107
+#define FRAME_pain121         	108
+#define FRAME_pain201         	109
+#define FRAME_pain202         	110
+#define FRAME_pain203         	111
+#define FRAME_pain204         	112
+#define FRAME_pain205         	113
+#define FRAME_pain206         	114
+#define FRAME_pain207         	115
+#define FRAME_pain208         	116
+#define FRAME_pain301         	117
+#define FRAME_pain302         	118
+#define FRAME_pain303         	119
+#define FRAME_pain304         	120
+#define FRAME_pain305         	121
+#define FRAME_pain306         	122
+#define FRAME_death101        	123
+#define FRAME_death102        	124
+#define FRAME_death103        	125
+#define FRAME_death104        	126
+#define FRAME_death105        	127
+#define FRAME_death106        	128
+#define FRAME_death107        	129
+#define FRAME_death108        	130
+#define FRAME_death109        	131
+#define FRAME_death110        	132
+#define FRAME_death111        	133
+#define FRAME_death112        	134
+#define FRAME_death113        	135
+#define FRAME_death114        	136
+#define FRAME_death115        	137
+#define FRAME_death116        	138
+#define FRAME_death117        	139
+#define FRAME_death118        	140
+#define FRAME_death201        	141
+#define FRAME_death202        	142
+#define FRAME_death203        	143
+#define FRAME_death204        	144
+#define FRAME_death205        	145
+#define FRAME_duck01          	146
+#define FRAME_duck02          	147
+#define FRAME_duck03          	148
+#define FRAME_duck04          	149
+#define FRAME_duck05          	150
+#define FRAME_duck06          	151
+#define FRAME_duck07          	152
+#define FRAME_duck08          	153
+#define FRAME_defens01        	154
+#define FRAME_defens02        	155
+#define FRAME_defens03        	156
+#define FRAME_defens04        	157
+#define FRAME_defens05        	158
+#define FRAME_defens06        	159
+#define FRAME_defens07        	160
+#define FRAME_defens08        	161
+#define FRAME_stand01         	162
+#define FRAME_stand02         	163
+#define FRAME_stand03         	164
+#define FRAME_stand04         	165
+#define FRAME_stand05         	166
+#define FRAME_stand06         	167
+#define FRAME_stand07         	168
+#define FRAME_stand08         	169
+#define FRAME_stand09         	170
+#define FRAME_stand10         	171
+#define FRAME_stand11         	172
+#define FRAME_stand12         	173
+#define FRAME_stand13         	174
+#define FRAME_stand14         	175
+#define FRAME_stand15         	176
+#define FRAME_stand16         	177
+#define FRAME_stand17         	178
+#define FRAME_stand18         	179
+#define FRAME_stand19         	180
+#define FRAME_stand20         	181
+#define FRAME_stand21         	182
+#define FRAME_stand22         	183
+#define FRAME_stand23         	184
+#define FRAME_stand24         	185
+#define FRAME_stand25         	186
+#define FRAME_stand26         	187
+#define FRAME_stand27         	188
+#define FRAME_stand28         	189
+#define FRAME_stand29         	190
+#define FRAME_stand30         	191
+#define FRAME_stand31         	192
+#define FRAME_stand32         	193
+#define FRAME_stand33         	194
+#define FRAME_stand34         	195
+#define FRAME_stand35         	196
+#define FRAME_stand36         	197
+#define FRAME_stand37         	198
+#define FRAME_stand38         	199
+#define FRAME_stand39         	200
+#define FRAME_stand40         	201
+#define FRAME_stand41         	202
+#define FRAME_stand42         	203
+#define FRAME_stand43         	204
+#define FRAME_stand44         	205
+#define FRAME_stand45         	206
+#define FRAME_stand46         	207
+#define FRAME_stand47         	208
+#define FRAME_stand48         	209
+#define FRAME_stand49         	210
+#define FRAME_stand50         	211
+#define FRAME_stand51         	212
+#define FRAME_stand52         	213
+#define FRAME_stand53         	214
+#define FRAME_stand54         	215
+#define FRAME_stand55         	216
+#define FRAME_stand56         	217
+#define FRAME_stand57         	218
+#define FRAME_stand58         	219
+#define FRAME_stand59         	220
+#define FRAME_stand60         	221
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_carrier.c
@@ -1,0 +1,1301 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_carrier.h"
+
+// self->timestamp used for frame calculations in grenade & spawn code
+// self->wait used to prevent rapid refire of rocket launcher
+
+#define	CARRIER_ROCKET_TIME		2		// number of seconds between rocket shots
+#define CARRIER_ROCKET_SPEED	750
+#define	NUM_FLYERS_SPAWNED		6		// max # of flyers he can spawn
+
+#define	RAIL_FIRE_TIME			3
+
+void BossExplode (edict_t *self);
+void Grenade_Explode (edict_t *ent);
+
+qboolean infront (edict_t *self, edict_t *other);
+qboolean inback (edict_t *self, edict_t *other);
+qboolean below (edict_t *self, edict_t *other);
+void drawbbox (edict_t *self);
+
+//char *ED_NewString (char *string);
+void ED_CallSpawn (edict_t *ent);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+//static int	sound_search1;
+static int	sound_sight;
+static int	sound_rail;
+static int	sound_spawn;
+
+float	orig_yaw_speed;
+
+vec3_t flyer_mins = {-16, -16, -24};
+vec3_t flyer_maxs = {16, 16, 16};
+
+extern mmove_t flyer_move_attack2, flyer_move_attack3, flyer_move_kamikaze;
+
+
+void carrier_run (edict_t *self);
+void carrier_stand (edict_t *self);
+void carrier_dead (edict_t *self);
+void carrier_attack (edict_t *self);
+void carrier_attack_mg (edict_t *self);
+void carrier_reattack_mg (edict_t *self);
+void carrier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+void carrier_attack_gren (edict_t *self);
+void carrier_reattack_gren (edict_t *self);
+
+void carrier_start_spawn (edict_t *self);
+void carrier_spawn_check (edict_t *self);
+void carrier_prep_spawn (edict_t *self);
+
+void CarrierMachineGunHold (edict_t *self);
+void CarrierRocket (edict_t *self);
+
+
+void carrier_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+// code starts here
+//void carrier_search (edict_t *self)
+//{
+//	if (random() < 0.5)
+//		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+//}
+
+//
+// this is the smarts for the rocket launcher in coop
+//
+// if there is a player behind/below the carrier, and we can shoot, and we can trace a LOS to them ..
+// pick one of the group, and let it rip
+void CarrierCoopCheck (edict_t *self)
+{
+	// no more than 4 players in coop, so..
+	edict_t *targets[4];
+	int		num_targets = 0, target, player;
+	edict_t *ent;
+	trace_t	tr;
+
+	// if we're not in coop, this is a noop
+	if (!coop || !coop->value)
+		return;
+	// if we are, and we have recently fired, bail
+	if (self->wait > level.time)
+		return;
+
+	memset (targets, 0, 4*sizeof(edict_t *));
+
+	// cycle through players
+	for (player = 1; player <= game.maxclients; player++)
+	{
+		ent = &g_edicts[player];
+		if (!ent->inuse)
+			continue;
+		if (!ent->client)
+			continue;
+		if (inback(self, ent) || below(self, ent))
+		{
+			tr = gi.trace (self->s.origin, NULL, NULL, ent->s.origin, self, MASK_SOLID);
+			if (tr.fraction == 1.0)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("Carrier: found a player who I can shoot\n");
+				targets[num_targets++] = ent;
+			}
+		}
+	}
+
+	if (!num_targets)
+		return;
+
+	// get a number from 0 to (num_targets-1)
+	target = qrandom() * num_targets;
+	
+	// just in case we got a 1.0 from random
+	if (target == num_targets)
+		target--;
+
+	// make sure to prevent rapid fire rockets
+	self->wait = level.time + CARRIER_ROCKET_TIME;
+
+	// save off the real enemy
+	ent = self->enemy;
+	// set the new guy as temporary enemy
+	self->enemy = targets[target];
+	CarrierRocket (self);
+	// put the real enemy back
+	self->enemy = ent;
+
+	// we're done
+	return;
+}
+
+void CarrierGrenade (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	aim;
+	int		flash_number;
+	float	direction;		// from lower left to upper right, or lower right to upper left
+	float	spreadR, spreadU;
+	int		mytime;
+
+	CarrierCoopCheck(self);
+
+	if (!self->enemy)
+		return;
+
+	if (qrandom() < 0.5)
+		direction = -1.0;
+	else
+		direction = 1.0;
+
+	mytime = (int)((level.time - self->timestamp)/0.4);
+
+	if (mytime == 0)
+	{
+		spreadR = 0.15 * direction;
+//		spreadU = 0.1 * direction;
+		spreadU = 0.1 - 0.1 * direction;
+	}
+	else if (mytime == 1)
+	{
+		spreadR = 0;
+//		spreadU = 0;
+		spreadU = 0.1;
+	}
+	else if (mytime == 2)
+	{
+		spreadR = -0.15 * direction;
+//		spreadU = -0.1 * direction;
+		spreadU = 0.1 - -0.1 * direction;
+	}
+	else if (mytime == 3)
+	{
+		spreadR = 0;
+//		spreadU = 0;
+		spreadU = 0.1;
+	}
+	else
+	{
+		// error, shoot straight
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("CarrierGrenade: bad time  %2.2f   %2.2f\n", level.time, self->timestamp);
+		spreadR = 0;
+		spreadU = 0;
+	}
+
+	AngleVectors (self->s.angles, forward, right, up);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_GRENADE], forward, right, start);
+
+	VectorSubtract (self->enemy->s.origin, start, aim);
+	VectorNormalize (aim);
+
+	VectorMA (aim, spreadR, right, aim);
+	VectorMA (aim, spreadU, up, aim);
+
+	if(aim[2] > 0.15)
+		aim[2] = 0.15;
+	else if(aim[2] < -0.5)
+		aim[2] = -0.5;
+
+	flash_number = MZ2_GUNNER_GRENADE_1;
+	monster_fire_grenade (self, start, aim, 50, 600, flash_number);
+}
+
+void CarrierPredictiveRocket  (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf("predictive fire\n");
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start);
+	PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.3, dir, NULL);
+	monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_1);
+
+//2
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start);
+	PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, -0.15, dir, NULL);
+	monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_2);
+
+//3
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start);
+	PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0, dir, NULL);
+	monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_3);
+
+//4
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start);
+	PredictAim (self->enemy, start, CARRIER_ROCKET_SPEED, false, 0.15, dir, NULL);
+	monster_fire_rocket (self, start, dir, 50, CARRIER_ROCKET_SPEED, MZ2_CARRIER_ROCKET_4);
+}	
+
+void CarrierRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	if(self->enemy)
+	{
+		if(self->enemy->client && qrandom() < 0.5)
+		{
+			CarrierPredictiveRocket(self);
+			return;
+		}
+	}
+	else
+		return;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_1], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	vec[2] -= 15;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, 0.4, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_1);
+
+//2
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_2], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, 0.025, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_2);
+
+//3
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_3], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, -0.025, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_3);
+
+//4
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_ROCKET_4], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+	vec[2] -= 15;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	VectorMA (dir, -0.4, right, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_CARRIER_ROCKET_4);
+
+//5
+//	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+//	VectorSubtract (vec, start, dir);
+//	VectorNormalize (dir);
+//	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+}	
+
+void carrier_firebullet_right (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+	int		flashnum;
+
+	// if we're in manual steering mode, it means we're leaning down .. use the lower shot
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		flashnum = MZ2_CARRIER_MACHINEGUN_R2;
+	else
+		flashnum = MZ2_CARRIER_MACHINEGUN_R1;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+
+//	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+/*
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (start);
+			gi.WritePosition (target);
+			gi.multicast (start, MULTICAST_ALL);	
+*/
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum);
+}	
+
+void carrier_firebullet_left (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+	int		flashnum;
+
+	// if we're in manual steering mode, it means we're leaning down .. use the lower shot
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		flashnum = MZ2_CARRIER_MACHINEGUN_L2;
+	else
+		flashnum = MZ2_CARRIER_MACHINEGUN_L1;
+	
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+
+//	VectorMA (self->enemy->s.origin, 0.2, self->enemy->velocity, target);
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+/*
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (start);
+			gi.WritePosition (target);
+			gi.multicast (start, MULTICAST_ALL);	
+*/
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD*3, DEFAULT_BULLET_VSPREAD, flashnum);
+}	
+
+void CarrierMachineGun (edict_t *self)
+{
+	CarrierCoopCheck(self);
+	if (self->enemy)
+		carrier_firebullet_left(self);
+	if (self->enemy)
+		carrier_firebullet_right(self);
+}	
+
+void CarrierSpawn (edict_t *self)
+{
+	vec3_t	f, r, offset, startpoint, spawnpoint;
+	edict_t	*ent;
+	int		mytime;
+
+//	VectorSet (offset, 105, 0, -30); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8
+	VectorSet (offset, 105, 0, -58); // real distance needed is (sqrt (56*56*2) + sqrt(16*16*2)) or 101.8
+	AngleVectors (self->s.angles, f, r, NULL);
+
+	G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+
+	// the +0.1 is because level.time is sometimes a little low
+	mytime = (int)((level.time + 0.1 - self->timestamp)/0.5);
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("mytime = %d, (%2.2f)\n", mytime, level.time - self->timestamp);
+
+	if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
+	{
+		// the second flier should be a kamikaze flyer
+		if (mytime != 2)
+			ent = CreateMonster (spawnpoint, self->s.angles, "monster_flyer");
+		else
+			ent = CreateMonster (spawnpoint, self->s.angles, "monster_kamikaze");
+
+		if (!ent)
+			return;
+
+		gi.sound (self, CHAN_BODY, sound_spawn, 1, ATTN_NONE, 0);
+
+		self->monsterinfo.monster_slots--;
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("carrier: post-spawn : %d slots left\n", self->monsterinfo.monster_slots);
+
+		ent->nextthink = level.time;
+		ent->think (ent);
+		
+		ent->monsterinfo.aiflags |= AI_SPAWNED_CARRIER|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
+		ent->monsterinfo.commander = self;
+
+		if ((self->enemy->inuse) && (self->enemy->health > 0))
+		{
+			ent->enemy = self->enemy;
+			FoundTarget (ent);
+			if (mytime == 1)
+			{
+				ent->monsterinfo.lefty = 0;
+				ent->monsterinfo.attack_state = AS_SLIDING;
+				ent->monsterinfo.currentmove = &flyer_move_attack3;
+			}
+			else if (mytime == 2)
+			{
+				ent->monsterinfo.lefty = 0;
+				ent->monsterinfo.attack_state = AS_STRAIGHT;
+				ent->monsterinfo.currentmove = &flyer_move_kamikaze;
+				ent->mass = 100;
+				ent->monsterinfo.aiflags |= AI_CHARGING;
+			}
+			else if (mytime == 3)
+			{
+				ent->monsterinfo.lefty = 1;
+				ent->monsterinfo.attack_state = AS_SLIDING;
+				ent->monsterinfo.currentmove = &flyer_move_attack3;
+			}
+//			else if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("carrier:  unexpected time %d!\n", mytime);
+		}
+	}
+}
+
+void carrier_prep_spawn (edict_t *self)
+{
+	CarrierCoopCheck(self);
+	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+	self->timestamp = level.time;
+	self->yaw_speed = 10;
+	CarrierMachineGun(self);
+}
+
+void carrier_spawn_check (edict_t *self)
+{
+//	gi.dprintf ("times - %2.2f %2.2f\n", level.time, self->timestamp);
+	CarrierCoopCheck(self);
+	CarrierMachineGun(self);
+	CarrierSpawn (self);
+
+	if (level.time > (self->timestamp + 1.1))  // 0.5 seconds per flyer.  this gets three
+	{
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		self->yaw_speed = orig_yaw_speed;
+		return;
+	}
+	else
+		self->monsterinfo.nextframe = FRAME_spawn08;
+}
+
+void carrier_ready_spawn (edict_t *self)
+{
+	float	current_yaw;
+	vec3_t	offset, f, r, startpoint, spawnpoint;
+
+	CarrierCoopCheck(self);
+	CarrierMachineGun(self);
+
+	current_yaw = anglemod(self->s.angles[YAW]);
+
+//	gi.dprintf ("yaws = %2.2f %2.2f\n", current_yaw, self->ideal_yaw);
+
+	if (fabs(current_yaw - self->ideal_yaw) > 0.1)
+	{
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+		self->timestamp += FRAMETIME;
+		return;
+	}
+
+	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+	VectorSet (offset, 105,0,-58);
+	AngleVectors (self->s.angles, f, r, NULL);
+	G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+	if (FindSpawnPoint (startpoint, flyer_mins, flyer_maxs, spawnpoint, 32))
+	{
+		SpawnGrow_Spawn (spawnpoint, 0);
+	}
+}
+
+void carrier_start_spawn (edict_t *self)
+{
+	int		mytime;
+	float	enemy_yaw;
+	vec3_t	temp;
+//	vec3_t	offset, f, r, startpoint;
+
+	CarrierCoopCheck(self);
+	if (!orig_yaw_speed)
+		orig_yaw_speed = self->yaw_speed;
+
+	if (!self->enemy)
+		return;
+
+	mytime = (int)((level.time - self->timestamp)/0.5);
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw2(temp);
+
+	// note that the offsets are based on a forward of 105 from the end angle
+	if (mytime == 0)
+	{
+		self->ideal_yaw = anglemod(enemy_yaw - 30);
+//		VectorSet (offset, 90.9, 52.5, 0);
+	}
+	else if (mytime == 1)
+	{
+		self->ideal_yaw = anglemod(enemy_yaw);
+//		VectorSet (offset, 90.9, -52.5, 0);
+	}
+	else if (mytime == 2)
+	{
+		self->ideal_yaw = anglemod(enemy_yaw + 30);
+//		VectorSet (offset, 90.9, -52.5, 0);
+	}
+//	else if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("carrier: bad spawntime\n");
+
+	CarrierMachineGun (self);
+}
+
+mframe_t carrier_frames_stand [] =
+{
+//	ai_stand, 0, drawbbox,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	carrier_move_stand = {FRAME_search01, FRAME_search13, carrier_frames_stand, NULL};
+
+mframe_t carrier_frames_walk [] =
+{
+//	ai_walk,	12,	drawbbox,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL
+};
+mmove_t carrier_move_walk = {FRAME_search01, FRAME_search13, carrier_frames_walk, NULL};
+
+
+mframe_t carrier_frames_run [] =
+{
+//	ai_run,	12,	drawbbox,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck,
+	ai_run,	6,	CarrierCoopCheck
+};
+mmove_t carrier_move_run = {FRAME_search01, FRAME_search13, carrier_frames_run, NULL};
+
+mframe_t carrier_frames_attack_pre_mg [] =
+{
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	carrier_attack_mg
+};
+mmove_t carrier_move_attack_pre_mg = {FRAME_firea01, FRAME_firea08, carrier_frames_attack_pre_mg, NULL};
+
+
+// Loop this
+mframe_t carrier_frames_attack_mg [] =
+{
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	carrier_reattack_mg
+/*
+	ai_charge,	0,	CarrierMachineGunHold,
+//	ai_charge,	0,	CarrierMachineGun,
+	ai_charge,	0,	CarrierMachineGun,
+	ai_charge,	0,	carrier_reattack_mg
+*/
+};
+mmove_t carrier_move_attack_mg = {FRAME_firea09, FRAME_firea11, carrier_frames_attack_mg, NULL};
+
+mframe_t carrier_frames_attack_post_mg [] =
+{
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck,
+	ai_charge,	4,	CarrierCoopCheck
+};
+mmove_t carrier_move_attack_post_mg = {FRAME_firea12, FRAME_firea15, carrier_frames_attack_post_mg, carrier_run};
+
+mframe_t carrier_frames_attack_pre_gren [] =
+{
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, carrier_attack_gren
+};
+mmove_t carrier_move_attack_pre_gren = {FRAME_fireb01, FRAME_fireb06, carrier_frames_attack_pre_gren, NULL};
+
+mframe_t carrier_frames_attack_gren [] =
+{
+	ai_charge, -15, CarrierGrenade,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, carrier_reattack_gren
+};
+mmove_t carrier_move_attack_gren = {FRAME_fireb07, FRAME_fireb10, carrier_frames_attack_gren, NULL};
+
+mframe_t carrier_frames_attack_post_gren [] =
+{
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck,
+	ai_charge, 4, CarrierCoopCheck
+};
+mmove_t carrier_move_attack_post_gren = {FRAME_fireb11, FRAME_fireb16, carrier_frames_attack_post_gren, carrier_run};
+
+mframe_t carrier_frames_attack_rocket [] =
+{
+	ai_charge,	15,	CarrierRocket
+};
+mmove_t carrier_move_attack_rocket = {FRAME_fireb01, FRAME_fireb01, carrier_frames_attack_rocket, carrier_run};
+
+void CarrierRail (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	CarrierCoopCheck(self);
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CARRIER_RAILGUN], forward, right, start);
+
+	// calc direction to where we targeted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, 50, 100, MZ2_CARRIER_RAILGUN);
+	self->monsterinfo.attack_finished = level.time + RAIL_FIRE_TIME;
+}
+
+void CarrierSaveLoc (edict_t *self)
+{
+	CarrierCoopCheck(self);
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+};
+
+mframe_t carrier_frames_attack_rail [] =
+{
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, 2, CarrierSaveLoc,
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, -20, CarrierRail,
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, 2, CarrierCoopCheck,
+	ai_charge, 2, CarrierCoopCheck
+};
+mmove_t carrier_move_attack_rail = {FRAME_search01, FRAME_search09, carrier_frames_attack_rail, carrier_run};
+
+mframe_t carrier_frames_spawn [] =
+{
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	carrier_prep_spawn,		// 7 - end of wind down
+	ai_charge,	-2,	carrier_start_spawn,		// 8 - start of spawn
+	ai_charge,	-2,	carrier_ready_spawn,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-10, carrier_spawn_check,		//12 - actual spawn
+	ai_charge,	-2,	CarrierMachineGun,		//13 - begin of wind down
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	CarrierMachineGun,
+	ai_charge,	-2,	carrier_reattack_mg		//18 - end of wind down
+};
+mmove_t carrier_move_spawn = {FRAME_spawn01, FRAME_spawn18, carrier_frames_spawn, NULL};
+
+mframe_t carrier_frames_pain_heavy [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t carrier_move_pain_heavy = {FRAME_death01, FRAME_death10, carrier_frames_pain_heavy, carrier_run};
+
+mframe_t carrier_frames_pain_light [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t carrier_move_pain_light = {FRAME_spawn01, FRAME_spawn04, carrier_frames_pain_light, carrier_run};
+
+mframe_t carrier_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode
+};
+mmove_t carrier_move_death = {FRAME_death01, FRAME_death16, carrier_frames_death, carrier_dead};
+
+void carrier_stand (edict_t *self)
+{
+//	gi.dprintf ("carrier stand\n");
+	self->monsterinfo.currentmove = &carrier_move_stand;
+}
+
+void carrier_run (edict_t *self)
+{
+
+//	gi.dprintf ("carrier run - %2.2f - %s \n", level.time, self->enemy->classname);
+	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &carrier_move_stand;
+	else
+		self->monsterinfo.currentmove = &carrier_move_run;
+}
+
+void carrier_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &carrier_move_walk;
+}
+
+void CarrierMachineGunHold (edict_t *self)
+{
+//	self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+//	self->yaw_speed = 0;
+//	self->monsterinfo.currentmove = &carrier_move_attack_mg;
+	CarrierMachineGun (self);
+}
+
+void carrier_attack (edict_t *self)
+{
+	vec3_t	vec;
+	float	range, luck;
+	qboolean	enemy_inback, enemy_infront, enemy_below;
+
+//	gi.dprintf ("carrier attack\n");
+	
+	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+		return;
+
+	enemy_inback = inback(self, self->enemy);
+	enemy_infront = infront (self, self->enemy);
+	enemy_below = below (self, self->enemy);
+
+	if (self->bad_area)
+	{
+		if ((enemy_inback) || (enemy_below))
+			self->monsterinfo.currentmove = &carrier_move_attack_rocket;
+		else if ((qrandom() < 0.1) || (level.time < self->monsterinfo.attack_finished))
+			self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+		else
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &carrier_move_attack_rail;
+		}
+		return;
+	}
+
+	if (self->monsterinfo.attack_state == AS_BLIND)
+	{
+		self->monsterinfo.currentmove = &carrier_move_spawn;
+		return;
+	}
+
+	if (!enemy_inback && !enemy_infront && !enemy_below) // to side and not under
+	{
+		if ((qrandom() < 0.1) || (level.time < self->monsterinfo.attack_finished)) 
+			self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+		else
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &carrier_move_attack_rail;
+		}
+		return;
+	}
+
+/*	if ((g_showlogic) && (g_showlogic->value))
+	{
+		gi.dprintf ("checking enemy ..");
+		if (enemy_inback)
+			gi.dprintf (" in back\n");
+		else if (enemy_infront)
+			gi.dprintf (" in front\n");
+		else
+			gi.dprintf (" inaccessible\n");
+	}
+*/	
+	if (enemy_infront)
+	{
+		VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+		range = VectorLength (vec);
+		if (range <= 125)
+		{
+			if ((qrandom() < 0.8) || (level.time < self->monsterinfo.attack_finished))
+				self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+			else
+			{
+				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &carrier_move_attack_rail;
+			}
+		}
+		else if (range < 600)
+		{
+			luck = qrandom();
+			if (self->monsterinfo.monster_slots > 2)
+			{
+				if (luck <= 0.20)
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+				else if (luck <= 0.40)
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_gren;
+				else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished))
+				{
+					gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+					self->monsterinfo.currentmove = &carrier_move_attack_rail;
+				}
+				else
+					self->monsterinfo.currentmove = &carrier_move_spawn;
+			}
+			else
+			{
+				if (luck <= 0.30)
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+				else if (luck <= 0.65)
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_gren;
+				else if (level.time >= self->monsterinfo.attack_finished)
+				{
+					gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+					self->monsterinfo.currentmove = &carrier_move_attack_rail;
+				}
+				else
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+			}
+		}
+		else // won't use grenades at this range
+		{
+			luck = qrandom();
+			if (self->monsterinfo.monster_slots > 2)
+			{
+				if (luck < 0.3)
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+				else if ((luck < 0.65) && !(level.time < self->monsterinfo.attack_finished))
+				{
+					gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+					VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+					self->pos1[2] += self->enemy->viewheight;
+					self->monsterinfo.currentmove = &carrier_move_attack_rail;
+				}
+				else
+					self->monsterinfo.currentmove = &carrier_move_spawn;
+			}
+			else
+			{
+				if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished))
+					self->monsterinfo.currentmove = &carrier_move_attack_pre_mg;
+				else
+				{
+					gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+					self->monsterinfo.currentmove = &carrier_move_attack_rail;
+				}
+			}
+		}
+	}
+	else if ((enemy_below) || (enemy_inback))
+	{
+		self->monsterinfo.currentmove = &carrier_move_attack_rocket;
+	}
+}
+
+void carrier_attack_mg (edict_t *self)
+{
+	CarrierCoopCheck(self);
+	self->monsterinfo.currentmove = &carrier_move_attack_mg;
+}
+
+void carrier_reattack_mg (edict_t *self)
+{
+	CarrierCoopCheck(self);
+	if ( infront(self, self->enemy) )
+		if (qrandom() <= 0.5)
+			if ((qrandom() < 0.7) || (self->monsterinfo.monster_slots <= 2))
+				self->monsterinfo.currentmove = &carrier_move_attack_mg;
+			else
+				self->monsterinfo.currentmove = &carrier_move_spawn;
+		else
+			self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
+	else
+		self->monsterinfo.currentmove = &carrier_move_attack_post_mg;
+}
+
+
+void carrier_attack_gren (edict_t *self)
+{
+//	gi.dprintf ("carrier_attack_gren - %2.2f\n",level.time);
+	CarrierCoopCheck(self);
+	self->timestamp = level.time;
+	self->monsterinfo.currentmove = &carrier_move_attack_gren;
+}
+
+void carrier_reattack_gren (edict_t *self)
+{
+	CarrierCoopCheck(self);
+//	gi.dprintf ("carrier_reattack - %2.2f", level.time);
+	if ( infront(self, self->enemy) )
+		if (self->timestamp + 1.3 > level.time ) // four grenades
+		{
+//			gi.dprintf (" attacking\n");
+			self->monsterinfo.currentmove = &carrier_move_attack_gren;
+			return;
+		}
+//	gi.dprintf ("not attacking\n");
+	self->monsterinfo.currentmove = &carrier_move_attack_post_gren;
+}
+
+
+void carrier_pain (edict_t *self, edict_t *, float, int damage)
+{
+	qboolean changed = false;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	//	gi.dprintf ("carrier pain\n");
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 5;
+
+	if (damage < 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+	}
+	else if (damage < 30)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+		if (qrandom() < 0.5)
+		{
+			changed = true;
+			self->monsterinfo.currentmove = &carrier_move_pain_light;
+		}
+	}
+	else 
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &carrier_move_pain_heavy;
+		changed = true;
+	}
+
+	// if we changed frames, clean up our little messes
+	if (changed)
+	{
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		self->yaw_speed = orig_yaw_speed;
+	}
+}
+
+void carrier_dead (edict_t *self)
+{
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void carrier_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &carrier_move_death;
+}
+
+qboolean Carrier_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance = 0.0;
+	trace_t	tr;
+	qboolean	enemy_infront, enemy_inback, enemy_below;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// go ahead and spawn stuff if we're mad a a client
+			if (self->enemy->client && self->monsterinfo.monster_slots > 2)
+			{
+				self->monsterinfo.attack_state = AS_BLIND;
+				return true;
+			}
+				
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+				return false;
+		}
+	}
+	
+	enemy_infront = infront(self, self->enemy);
+	enemy_inback = inback(self, self->enemy);
+	enemy_below = below (self, self->enemy);
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw2(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+	// PMM - shoot out the back if appropriate
+	if ((enemy_inback) || (!enemy_infront && enemy_below))
+	{
+		// this is using wait because the attack is supposed to be independent
+		if (level.time >= self->wait)
+		{
+			self->wait = level.time + CARRIER_ROCKET_TIME;
+			self->monsterinfo.attack(self);
+			if (qrandom() < 0.6)
+				self->monsterinfo.attack_state = AS_SLIDING;
+			else
+				self->monsterinfo.attack_state = AS_STRAIGHT;
+			return true;
+		}
+	}
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+//	if (level.time < self->monsterinfo.attack_finished)
+//		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_FAR)
+	{
+		chance = 0.5;
+	}
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	if ((qrandom () < chance) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+//		self->monsterinfo.attack_finished = level.time + 2*random();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.6)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+void CarrierPrecache (void)
+{
+	gi.soundindex ("flyer/flysght1.wav");
+	gi.soundindex ("flyer/flysrch1.wav");
+	gi.soundindex ("flyer/flypain1.wav");
+	gi.soundindex ("flyer/flypain2.wav");
+	gi.soundindex ("flyer/flyatck2.wav");
+	gi.soundindex ("flyer/flyatck1.wav");
+	gi.soundindex ("flyer/flydeth1.wav");
+	gi.soundindex ("flyer/flyatck3.wav");
+	gi.soundindex ("flyer/flyidle1.wav");
+	gi.soundindex ("weapons/rockfly.wav");
+	gi.soundindex ("infantry/infatck1.wav");
+	gi.soundindex ("gunner/gunatck3.wav");
+	gi.soundindex ("weapons/grenlb1b.wav");
+	gi.soundindex ("tank/rocket.wav");
+
+	gi.modelindex ("models/monsters/flyer/tris.md2");
+	gi.modelindex ("models/objects/rocket/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+	gi.modelindex ("models/objects/grenade/tris.md2");
+	gi.modelindex("models/items/spawngro/tris.md2");
+	gi.modelindex("models/items/spawngro2/tris.md2");
+	gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
+	gi.modelindex ("models/objects/gibs/gear/tris.md2");
+}
+
+
+/*QUAKED monster_carrier (1 .5 0) (-56 -56 -44) (56 56 44) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_carrier (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("carrier/pain_md.wav");
+	sound_pain2 = gi.soundindex ("carrier/pain_lg.wav");
+	sound_pain3 = gi.soundindex ("carrier/pain_sm.wav");
+	sound_death = gi.soundindex ("carrier/death.wav");
+//	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
+	sound_rail = gi.soundindex ("gladiator/railgun.wav");
+	sound_sight = gi.soundindex ("carrier/sight.wav");
+	sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav");
+
+	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/carrier/tris.md2");
+	VectorSet (self->mins, -56, -56, -44);
+	VectorSet (self->maxs, 56, 56, 44);
+
+	// 2000 - 4000 health
+	self->health = qmax(2000, 2000 + 1000*((skill->value)-1));
+	// add health in coop (500 * skill)
+	if (coop->value)
+		self->health += 500*(skill->value);	
+
+	self->gib_health = -200;
+	self->mass = 1000;
+
+	self->yaw_speed = 15;
+	orig_yaw_speed = self->yaw_speed;
+//	self->yaw_speed = 1;
+	
+	self->flags |= FL_IMMUNE_LASER;
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+	self->pain = carrier_pain;
+	self->die = carrier_die;
+
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.stand = carrier_stand;
+	self->monsterinfo.walk = carrier_walk;
+	self->monsterinfo.run = carrier_run;
+	self->monsterinfo.attack = carrier_attack;
+//	self->monsterinfo.search = carrier_search;
+	self->monsterinfo.sight = carrier_sight;
+	self->monsterinfo.checkattack = Carrier_CheckAttack;
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &carrier_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	CarrierPrecache();
+
+	flymonster_start (self);
+
+	self->monsterinfo.attack_finished = 0;
+	switch ((int)skill->value)
+	{
+	case 0:
+		self->monsterinfo.monster_slots = 3;
+		break;
+	case 1:
+	case 2:
+		self->monsterinfo.monster_slots = 6;
+		break;
+	case 3:
+		self->monsterinfo.monster_slots = 9;
+		break;
+	default:
+		self->monsterinfo.monster_slots = 6;
+		break;
+	}
+}
+
--- /dev/null
+++ b/rogue/m_carrier.h
@@ -1,0 +1,84 @@
+// G:\quake2\xpack\models/monsters/carrier
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_search01        	0
+#define FRAME_search02        	1
+#define FRAME_search03        	2
+#define FRAME_search04        	3
+#define FRAME_search05        	4
+#define FRAME_search06        	5
+#define FRAME_search07        	6
+#define FRAME_search08        	7
+#define FRAME_search09        	8
+#define FRAME_search10        	9
+#define FRAME_search11        	10
+#define FRAME_search12        	11
+#define FRAME_search13        	12
+#define FRAME_firea01         	13
+#define FRAME_firea02         	14
+#define FRAME_firea03         	15
+#define FRAME_firea04         	16
+#define FRAME_firea05         	17
+#define FRAME_firea06         	18
+#define FRAME_firea07         	19
+#define FRAME_firea08         	20
+#define FRAME_firea09         	21
+#define FRAME_firea10         	22
+#define FRAME_firea11         	23
+#define FRAME_firea12         	24
+#define FRAME_firea13         	25
+#define FRAME_firea14         	26
+#define FRAME_firea15         	27
+#define FRAME_fireb01         	28
+#define FRAME_fireb02         	29
+#define FRAME_fireb03         	30
+#define FRAME_fireb04         	31
+#define FRAME_fireb05         	32
+#define FRAME_fireb06         	33
+#define FRAME_fireb07         	34
+#define FRAME_fireb08         	35
+#define FRAME_fireb09         	36
+#define FRAME_fireb10         	37
+#define FRAME_fireb11         	38
+#define FRAME_fireb12         	39
+#define FRAME_fireb13         	40
+#define FRAME_fireb14         	41
+#define FRAME_fireb15         	42
+#define FRAME_fireb16         	43
+#define FRAME_spawn01         	44
+#define FRAME_spawn02         	45
+#define FRAME_spawn03         	46
+#define FRAME_spawn04         	47
+#define FRAME_spawn05         	48
+#define FRAME_spawn06         	49
+#define FRAME_spawn07         	50
+#define FRAME_spawn08         	51
+#define FRAME_spawn09         	52
+#define FRAME_spawn10         	53
+#define FRAME_spawn11         	54
+#define FRAME_spawn12         	55
+#define FRAME_spawn13         	56
+#define FRAME_spawn14         	57
+#define FRAME_spawn15         	58
+#define FRAME_spawn16         	59
+#define FRAME_spawn17         	60
+#define FRAME_spawn18         	61
+#define FRAME_death01         	62
+#define FRAME_death02         	63
+#define FRAME_death03         	64
+#define FRAME_death04         	65
+#define FRAME_death05         	66
+#define FRAME_death06         	67
+#define FRAME_death07         	68
+#define FRAME_death08         	69
+#define FRAME_death09         	70
+#define FRAME_death10         	71
+#define FRAME_death11         	72
+#define FRAME_death12         	73
+#define FRAME_death13         	74
+#define FRAME_death14         	75
+#define FRAME_death15         	76
+#define FRAME_death16         	77
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_chick.c
@@ -1,0 +1,942 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_chick.h"
+
+// ROGUE
+#define LEAD_TARGET		1
+// ROGUE
+
+qboolean visible (edict_t *self, edict_t *other);
+
+void chick_stand (edict_t *self);
+void chick_run (edict_t *self);
+void chick_reslash(edict_t *self);
+void chick_rerocket(edict_t *self);
+void chick_attack1(edict_t *self);
+
+static int	sound_missile_prelaunch;
+static int	sound_missile_launch;
+static int	sound_melee_swing;
+static int	sound_melee_hit;
+static int	sound_missile_reload;
+static int	sound_death1;
+static int	sound_death2;
+static int	sound_fall_down;
+static int	sound_idle1;
+static int	sound_idle2;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_sight;
+static int	sound_search;
+
+void ChickMoan (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0);
+}
+
+mframe_t chick_frames_fidget [] =
+{
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  ChickMoan,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL
+};
+mmove_t chick_move_fidget = {FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand};
+
+void chick_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() <= 0.3)
+		self->monsterinfo.currentmove = &chick_move_fidget;
+}
+
+mframe_t chick_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, chick_fidget,
+
+};
+mmove_t chick_move_stand = {FRAME_stand101, FRAME_stand130, chick_frames_stand, NULL};
+
+void chick_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_stand;
+}
+
+mframe_t chick_frames_start_run [] =
+{
+	ai_run, 1,  NULL,
+	ai_run, 0,  NULL,
+	ai_run, 0,	 NULL,
+	ai_run, -1, NULL, 
+	ai_run, -1, NULL, 
+	ai_run, 0,  NULL,
+	ai_run, 1,  NULL,
+	ai_run, 3,  NULL,
+	ai_run, 6,	 NULL,
+	ai_run, 3,	 NULL
+};
+mmove_t chick_move_start_run = {FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run};
+
+mframe_t chick_frames_run [] =
+{
+	ai_run, 6,	NULL,
+	ai_run, 8,  NULL,
+	ai_run, 13, NULL,
+	ai_run, 5,  monster_done_dodge,  // make sure to clear dodge bit
+	ai_run, 7,  NULL,
+	ai_run, 4,  NULL,
+	ai_run, 11, NULL,
+	ai_run, 5,  NULL,
+	ai_run, 9,  NULL,
+	ai_run, 7,  NULL
+};
+
+mmove_t chick_move_run = {FRAME_walk11, FRAME_walk20, chick_frames_run, NULL};
+
+mframe_t chick_frames_walk [] =
+{
+	ai_walk, 6,	 NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 13, NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 11, NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 9,  NULL,
+	ai_walk, 7,  NULL
+};
+
+mmove_t chick_move_walk = {FRAME_walk11, FRAME_walk20, chick_frames_walk, NULL};
+
+void chick_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_walk;
+}
+
+void chick_run (edict_t *self)
+{
+	monster_done_dodge (self);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &chick_move_stand;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &chick_move_walk ||
+		self->monsterinfo.currentmove == &chick_move_start_run)
+	{
+		self->monsterinfo.currentmove = &chick_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &chick_move_start_run;
+	}
+}
+
+mframe_t chick_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t chick_move_pain1 = {FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run};
+
+mframe_t chick_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t chick_move_pain2 = {FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run};
+
+mframe_t chick_frames_pain3 [] =
+{
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, -6,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, 11,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 4,		NULL,
+	ai_move, 1,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, -3,	NULL,
+	ai_move, -4,	NULL,
+	ai_move, 5,		NULL,
+	ai_move, 7,		NULL,
+	ai_move, -2,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, -5,	NULL,
+	ai_move, -2,	NULL,
+	ai_move, -8,	NULL,
+	ai_move, 2,		NULL
+};
+mmove_t chick_move_pain3 = {FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run};
+
+void chick_pain (edict_t *self, edict_t *, float, int damage)
+{
+	float	r;
+
+	monster_done_dodge(self);
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	r = qrandom();
+	if (r < 0.33)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else if (r < 0.66)
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	// PMM - clear this from blindfire
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+	if (damage <= 10)
+		self->monsterinfo.currentmove = &chick_move_pain1;
+	else if (damage <= 25)
+		self->monsterinfo.currentmove = &chick_move_pain2;
+	else
+		self->monsterinfo.currentmove = &chick_move_pain3;
+
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+}
+
+void chick_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 16);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t chick_frames_death2 [] =
+{
+	ai_move, -6, NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -5, NULL,
+	ai_move, 0, NULL,
+	ai_move, -1,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 10, NULL,
+	ai_move, 2,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 2, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -5, NULL,
+	ai_move, 4, NULL,
+	ai_move, 15, NULL,
+	ai_move, 14, NULL,
+	ai_move, 1, NULL
+};
+mmove_t chick_move_death2 = {FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead};
+
+mframe_t chick_frames_death1 [] =
+{
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -7, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 11, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL
+	
+};
+mmove_t chick_move_death1 = {FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead};
+
+void chick_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 2;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &chick_move_death1;
+		gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &chick_move_death2;
+		gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+	}
+}
+
+// PMM - changes to duck code for new dodge
+
+mframe_t chick_frames_duck [] =
+{
+	ai_move, 0, monster_duck_down,
+	ai_move, 1, NULL,
+	ai_move, 4, monster_duck_hold,
+	ai_move, -4,  NULL,
+	ai_move, -5,  monster_duck_up,
+	ai_move, 3, NULL,
+	ai_move, 1,  NULL
+};
+mmove_t chick_move_duck = {FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run};
+
+/*
+void chick_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+// begin orig code
+	if (random() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &chick_move_duck;
+// end
+
+	float	r;
+	float	height;
+	int		shooting = 0;
+
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	r = qrandom();
+	if (r > (0.25*((skill->value)+1)))
+		return;
+
+	if ((self->monsterinfo.currentmove == &chick_move_start_attack1) ||
+		(self->monsterinfo.currentmove == &chick_move_attack1))
+	{
+		shooting = 1;
+	}
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		height = self->absmax[2];
+	}
+	else
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+	}
+
+	// check to see if it makes sense to duck
+	if (tr->endpos[2] <= height)
+	{
+		vec3_t right, diff;
+		if (shooting)
+		{
+			self->monsterinfo.attack_state = AS_SLIDING;
+			return;
+		}
+		AngleVectors (self->s.angles, NULL, right, NULL);
+		VectorSubtract (tr->endpos, self->s.origin, diff);
+		if (DotProduct (right, diff) < 0)
+		{
+			self->monsterinfo.lefty = 1;
+		}
+		// if it doesn't sense to duck, try to strafe away
+		monster_done_dodge (self);
+		self->monsterinfo.currentmove = &chick_move_run;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		return;
+	}
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &chick_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.aiflags |= AI_DODGING;
+		return;
+	}
+
+	if (!shooting)
+	{
+		self->monsterinfo.currentmove = &chick_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+		self->monsterinfo.aiflags |= AI_DODGING;
+	}
+	return;
+
+}
+*/
+void ChickSlash (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 10);
+	gi.sound (self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0);
+	fire_hit (self, aim, (10 + (rand() %6)), 100);
+}
+
+
+void ChickRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	trace_t	trace;	// PMM - check target
+	int		rocketSpeed;
+	float	dist;
+	// pmm - blindfire
+	vec3_t	target;
+	qboolean blindfire;
+
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		blindfire = true;
+	else
+		blindfire = false;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start);
+
+	rocketSpeed = 500 + (100 * skill->value);	// PGM rock & roll.... :)
+
+	// put a debug trail from start to endpoint, confirm that the start point is
+	// correct for the trace
+
+	// PMM
+	if (blindfire)
+		VectorCopy (self->monsterinfo.blind_fire_target, target);
+	else
+		VectorCopy (self->enemy->s.origin, target);
+	// pmm
+//PGM
+	// PMM - blindfire shooting
+	if (blindfire)
+	{
+		VectorCopy (target, vec);
+		VectorSubtract (vec, start, dir);
+	}
+	// pmm
+	// don't shoot at feet if they're above where i'm shooting from.
+	else if(qrandom() < 0.33 || (start[2] < self->enemy->absmin[2]))
+	{
+//		gi.dprintf("normal shot\n");
+		VectorCopy (target, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, dir);
+	}
+	else
+	{
+//		gi.dprintf("shooting at feet!\n");
+		VectorCopy (target, vec);
+		vec[2] = self->enemy->absmin[2];
+		VectorSubtract (vec, start, dir);
+	}
+//PGM
+
+//======
+//PMM - lead target  (not when blindfiring)
+	// 20, 35, 50, 65 chance of leading
+	if((!blindfire) && ((qrandom() < (0.2 + ((3 - skill->value) * 0.15)))))
+	{
+		float	time;
+
+//		gi.dprintf ("leading target\n");
+		dist = VectorLength (dir);
+		time = dist/rocketSpeed;
+		VectorMA(vec, time, self->enemy->velocity, vec);
+		VectorSubtract(vec, start, dir);
+	}
+//PMM - lead target
+//======
+
+	VectorNormalize (dir);
+
+	// pmm blindfire doesn't check target (done in checkattack)
+	// paranoia, make sure we're not shooting a target right next to us
+	trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+	if (blindfire)
+	{
+		// blindfire has different fail criteria for the trace
+		if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+			monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+		else 
+		{
+			// geez, this is bad.  she's avoiding about 80% of her blindfires due to hitting things.
+			// hunt around for a good shot
+			// try shifting the target to the left a little (to help counter her large offset)
+			VectorCopy (target, vec);
+			VectorMA (vec, -10, right, vec);
+			VectorSubtract(vec, start, dir);
+			VectorNormalize (dir);
+			trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+			if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+				monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+			else 
+			{
+				// ok, that failed.  try to the right
+				VectorCopy (target, vec);
+				VectorMA (vec, 10, right, vec);
+				VectorSubtract(vec, start, dir);
+				VectorNormalize (dir);
+				trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+				if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+					monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+//				else if ((g_showlogic) && (g_showlogic->value))
+//					// ok, I give up
+//					gi.dprintf ("chick avoiding blindfire shot\n");
+			}
+		}
+	}
+	else
+	{
+		trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+		if(trace.ent == self->enemy || trace.ent == WORLD)
+		{
+			if(trace.fraction > 0.5 || (trace.ent && trace.ent->client))
+				monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+	//		else
+	//			gi.dprintf("didn't make it halfway to target...aborting\n");
+		}
+	}
+}	
+
+void Chick_PreAttack1 (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0);
+}
+
+void ChickReload (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t chick_frames_start_attack1 [] =
+{
+	ai_charge, 0,	Chick_PreAttack1,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 4,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -3,  NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 7,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	chick_attack1
+};
+mmove_t chick_move_start_attack1 = {FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, NULL};
+
+
+mframe_t chick_frames_attack1 [] =
+{
+	ai_charge, 19,	ChickRocket,
+	ai_charge, -6,	NULL,
+	ai_charge, -5,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -7,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 10,	ChickReload,
+	ai_charge, 4,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 4,	NULL,
+	ai_charge, 3,	chick_rerocket
+
+};
+mmove_t chick_move_attack1 = {FRAME_attak114, FRAME_attak127, chick_frames_attack1, NULL};
+
+mframe_t chick_frames_end_attack1 [] =
+{
+	ai_charge, -3,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -6,	NULL,
+	ai_charge, -4,	NULL,
+	ai_charge, -2,  NULL
+};
+mmove_t chick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run};
+
+void chick_rerocket(edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		self->monsterinfo.currentmove = &chick_move_end_attack1;
+		return;
+	}
+	if (self->enemy->health > 0)
+	{
+		if (range (self, self->enemy) > RANGE_MELEE)
+			if ( visible (self, self->enemy) )
+				if (qrandom() <= (0.6 + (0.05*((float)skill->value))))
+				{
+					self->monsterinfo.currentmove = &chick_move_attack1;
+					return;
+				}
+	}	
+	self->monsterinfo.currentmove = &chick_move_end_attack1;
+}
+
+void chick_attack1(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_attack1;
+}
+
+mframe_t chick_frames_slash [] =
+{
+	ai_charge, 1,	NULL,
+	ai_charge, 7,	ChickSlash,
+	ai_charge, -7,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, -2,	chick_reslash
+};
+mmove_t chick_move_slash = {FRAME_attak204, FRAME_attak212, chick_frames_slash, NULL};
+
+mframe_t chick_frames_end_slash [] =
+{
+	ai_charge, -6,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -6,	NULL,
+	ai_charge, 0,	NULL
+};
+mmove_t chick_move_end_slash = {FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run};
+
+
+void chick_reslash(edict_t *self)
+{
+	if (self->enemy->health > 0)
+	{
+		if (range (self, self->enemy) == RANGE_MELEE)
+			if (qrandom() <= 0.9)
+			{				
+				self->monsterinfo.currentmove = &chick_move_slash;
+				return;
+			}
+			else
+			{
+				self->monsterinfo.currentmove = &chick_move_end_slash;
+				return;
+			}
+	}
+	self->monsterinfo.currentmove = &chick_move_end_slash;
+}
+
+void chick_slash(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_slash;
+}
+
+
+mframe_t chick_frames_start_slash [] =
+{	
+	ai_charge, 1,	NULL,
+	ai_charge, 8,	NULL,
+	ai_charge, 3,	NULL
+};
+mmove_t chick_move_start_slash = {FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash};
+
+
+
+void chick_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_start_slash;
+}
+
+
+void chick_attack(edict_t *self)
+{
+	float r, chance;
+
+	monster_done_dodge (self);
+
+	// PMM 
+	if (self->monsterinfo.attack_state == AS_BLIND)
+	{
+		// setup shot probabilities
+		if (self->monsterinfo.blind_fire_delay < 1.0)
+			chance = 1.0;
+		else if (self->monsterinfo.blind_fire_delay < 7.5)
+			chance = 0.4;
+		else
+			chance = 0.1;
+
+		r = qrandom();
+
+		// minimum of 2 seconds, plus 0-3, after the shots are done
+		self->monsterinfo.blind_fire_delay += 4.0 + 1.5 + qrandom();
+
+		// don't shoot at the origin
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+
+		// don't shoot if the dice say not to
+		if (r > chance)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("blindfire - NO SHOT\n");
+			return;
+		}
+
+		// turn on manual steering to signal both manual steering and blindfire
+		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+		self->monsterinfo.currentmove = &chick_move_start_attack1;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return;
+	}
+	// pmm
+
+	self->monsterinfo.currentmove = &chick_move_start_attack1;
+}
+
+void chick_sight(edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+//===========
+//PGM
+qboolean chick_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+void chick_duck (edict_t *self, float eta)
+{
+	if ((self->monsterinfo.currentmove == &chick_move_start_attack1) ||
+		(self->monsterinfo.currentmove == &chick_move_attack1))
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DUCKED;
+			return;
+		}
+	}
+
+	if (skill->value == 0)
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	else
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+
+	// has to be done immediately otherwise she can get stuck
+	monster_duck_down(self);
+
+	self->monsterinfo.nextframe = FRAME_duck01;
+	self->monsterinfo.currentmove = &chick_move_duck;
+	return;
+}
+
+void chick_sidestep (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &chick_move_start_attack1) ||
+		(self->monsterinfo.currentmove == &chick_move_attack1))
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DODGING;
+			return;
+		}
+	}
+
+	if (self->monsterinfo.currentmove != &chick_move_run)
+		self->monsterinfo.currentmove = &chick_move_run;
+}
+
+/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_chick (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_missile_prelaunch	= gi.soundindex ("chick/chkatck1.wav");	
+	sound_missile_launch	= gi.soundindex ("chick/chkatck2.wav");	
+	sound_melee_swing		= gi.soundindex ("chick/chkatck3.wav");	
+	sound_melee_hit			= gi.soundindex ("chick/chkatck4.wav");	
+	sound_missile_reload	= gi.soundindex ("chick/chkatck5.wav");	
+	sound_death1			= gi.soundindex ("chick/chkdeth1.wav");	
+	sound_death2			= gi.soundindex ("chick/chkdeth2.wav");	
+	sound_fall_down			= gi.soundindex ("chick/chkfall1.wav");	
+	sound_idle1				= gi.soundindex ("chick/chkidle1.wav");	
+	sound_idle2				= gi.soundindex ("chick/chkidle2.wav");	
+	sound_pain1				= gi.soundindex ("chick/chkpain1.wav");	
+	sound_pain2				= gi.soundindex ("chick/chkpain2.wav");	
+	sound_pain3				= gi.soundindex ("chick/chkpain3.wav");	
+	sound_sight				= gi.soundindex ("chick/chksght1.wav");	
+	sound_search			= gi.soundindex ("chick/chksrch1.wav");	
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/bitch2/tris.md2");
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 56);
+
+	self->health = 175;
+	self->gib_health = -70;
+	self->mass = 200;
+
+	self->pain = chick_pain;
+	self->die = chick_die;
+
+	self->monsterinfo.stand = chick_stand;
+	self->monsterinfo.walk = chick_walk;
+	self->monsterinfo.run = chick_run;
+	// pmm
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.duck = chick_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+	self->monsterinfo.sidestep = chick_sidestep;
+//	self->monsterinfo.dodge = chick_dodge;
+	// pmm
+	self->monsterinfo.attack = chick_attack;
+	self->monsterinfo.melee = chick_melee;
+	self->monsterinfo.sight = chick_sight;
+	self->monsterinfo.blocked = chick_blocked;		// PGM
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &chick_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	// PMM
+	self->monsterinfo.blindfire = true;
+	// pmm
+	walkmonster_start (self);
+}
+
--- /dev/null
+++ b/rogue/m_chick.h
@@ -1,0 +1,294 @@
+// G:\quake2\baseq2\models/monsters/bitch
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak119        	18
+#define FRAME_attak120        	19
+#define FRAME_attak121        	20
+#define FRAME_attak122        	21
+#define FRAME_attak123        	22
+#define FRAME_attak124        	23
+#define FRAME_attak125        	24
+#define FRAME_attak126        	25
+#define FRAME_attak127        	26
+#define FRAME_attak128        	27
+#define FRAME_attak129        	28
+#define FRAME_attak130        	29
+#define FRAME_attak131        	30
+#define FRAME_attak132        	31
+#define FRAME_attak201        	32
+#define FRAME_attak202        	33
+#define FRAME_attak203        	34
+#define FRAME_attak204        	35
+#define FRAME_attak205        	36
+#define FRAME_attak206        	37
+#define FRAME_attak207        	38
+#define FRAME_attak208        	39
+#define FRAME_attak209        	40
+#define FRAME_attak210        	41
+#define FRAME_attak211        	42
+#define FRAME_attak212        	43
+#define FRAME_attak213        	44
+#define FRAME_attak214        	45
+#define FRAME_attak215        	46
+#define FRAME_attak216        	47
+#define FRAME_death101        	48
+#define FRAME_death102        	49
+#define FRAME_death103        	50
+#define FRAME_death104        	51
+#define FRAME_death105        	52
+#define FRAME_death106        	53
+#define FRAME_death107        	54
+#define FRAME_death108        	55
+#define FRAME_death109        	56
+#define FRAME_death110        	57
+#define FRAME_death111        	58
+#define FRAME_death112        	59
+#define FRAME_death201        	60
+#define FRAME_death202        	61
+#define FRAME_death203        	62
+#define FRAME_death204        	63
+#define FRAME_death205        	64
+#define FRAME_death206        	65
+#define FRAME_death207        	66
+#define FRAME_death208        	67
+#define FRAME_death209        	68
+#define FRAME_death210        	69
+#define FRAME_death211        	70
+#define FRAME_death212        	71
+#define FRAME_death213        	72
+#define FRAME_death214        	73
+#define FRAME_death215        	74
+#define FRAME_death216        	75
+#define FRAME_death217        	76
+#define FRAME_death218        	77
+#define FRAME_death219        	78
+#define FRAME_death220        	79
+#define FRAME_death221        	80
+#define FRAME_death222        	81
+#define FRAME_death223        	82
+#define FRAME_duck01          	83
+#define FRAME_duck02          	84
+#define FRAME_duck03          	85
+#define FRAME_duck04          	86
+#define FRAME_duck05          	87
+#define FRAME_duck06          	88
+#define FRAME_duck07          	89
+#define FRAME_pain101         	90
+#define FRAME_pain102         	91
+#define FRAME_pain103         	92
+#define FRAME_pain104         	93
+#define FRAME_pain105         	94
+#define FRAME_pain201         	95
+#define FRAME_pain202         	96
+#define FRAME_pain203         	97
+#define FRAME_pain204         	98
+#define FRAME_pain205         	99
+#define FRAME_pain301         	100
+#define FRAME_pain302         	101
+#define FRAME_pain303         	102
+#define FRAME_pain304         	103
+#define FRAME_pain305         	104
+#define FRAME_pain306         	105
+#define FRAME_pain307         	106
+#define FRAME_pain308         	107
+#define FRAME_pain309         	108
+#define FRAME_pain310         	109
+#define FRAME_pain311         	110
+#define FRAME_pain312         	111
+#define FRAME_pain313         	112
+#define FRAME_pain314         	113
+#define FRAME_pain315         	114
+#define FRAME_pain316         	115
+#define FRAME_pain317         	116
+#define FRAME_pain318         	117
+#define FRAME_pain319         	118
+#define FRAME_pain320         	119
+#define FRAME_pain321         	120
+#define FRAME_stand101        	121
+#define FRAME_stand102        	122
+#define FRAME_stand103        	123
+#define FRAME_stand104        	124
+#define FRAME_stand105        	125
+#define FRAME_stand106        	126
+#define FRAME_stand107        	127
+#define FRAME_stand108        	128
+#define FRAME_stand109        	129
+#define FRAME_stand110        	130
+#define FRAME_stand111        	131
+#define FRAME_stand112        	132
+#define FRAME_stand113        	133
+#define FRAME_stand114        	134
+#define FRAME_stand115        	135
+#define FRAME_stand116        	136
+#define FRAME_stand117        	137
+#define FRAME_stand118        	138
+#define FRAME_stand119        	139
+#define FRAME_stand120        	140
+#define FRAME_stand121        	141
+#define FRAME_stand122        	142
+#define FRAME_stand123        	143
+#define FRAME_stand124        	144
+#define FRAME_stand125        	145
+#define FRAME_stand126        	146
+#define FRAME_stand127        	147
+#define FRAME_stand128        	148
+#define FRAME_stand129        	149
+#define FRAME_stand130        	150
+#define FRAME_stand201        	151
+#define FRAME_stand202        	152
+#define FRAME_stand203        	153
+#define FRAME_stand204        	154
+#define FRAME_stand205        	155
+#define FRAME_stand206        	156
+#define FRAME_stand207        	157
+#define FRAME_stand208        	158
+#define FRAME_stand209        	159
+#define FRAME_stand210        	160
+#define FRAME_stand211        	161
+#define FRAME_stand212        	162
+#define FRAME_stand213        	163
+#define FRAME_stand214        	164
+#define FRAME_stand215        	165
+#define FRAME_stand216        	166
+#define FRAME_stand217        	167
+#define FRAME_stand218        	168
+#define FRAME_stand219        	169
+#define FRAME_stand220        	170
+#define FRAME_stand221        	171
+#define FRAME_stand222        	172
+#define FRAME_stand223        	173
+#define FRAME_stand224        	174
+#define FRAME_stand225        	175
+#define FRAME_stand226        	176
+#define FRAME_stand227        	177
+#define FRAME_stand228        	178
+#define FRAME_stand229        	179
+#define FRAME_stand230        	180
+#define FRAME_walk01          	181
+#define FRAME_walk02          	182
+#define FRAME_walk03          	183
+#define FRAME_walk04          	184
+#define FRAME_walk05          	185
+#define FRAME_walk06          	186
+#define FRAME_walk07          	187
+#define FRAME_walk08          	188
+#define FRAME_walk09          	189
+#define FRAME_walk10          	190
+#define FRAME_walk11          	191
+#define FRAME_walk12          	192
+#define FRAME_walk13          	193
+#define FRAME_walk14          	194
+#define FRAME_walk15          	195
+#define FRAME_walk16          	196
+#define FRAME_walk17          	197
+#define FRAME_walk18          	198
+#define FRAME_walk19          	199
+#define FRAME_walk20          	200
+#define FRAME_walk21          	201
+#define FRAME_walk22          	202
+#define FRAME_walk23          	203
+#define FRAME_walk24          	204
+#define FRAME_walk25          	205
+#define FRAME_walk26          	206
+#define FRAME_walk27          	207
+#define FRAME_recln201        	208
+#define FRAME_recln202        	209
+#define FRAME_recln203        	210
+#define FRAME_recln204        	211
+#define FRAME_recln205        	212
+#define FRAME_recln206        	213
+#define FRAME_recln207        	214
+#define FRAME_recln208        	215
+#define FRAME_recln209        	216
+#define FRAME_recln210        	217
+#define FRAME_recln211        	218
+#define FRAME_recln212        	219
+#define FRAME_recln213        	220
+#define FRAME_recln214        	221
+#define FRAME_recln215        	222
+#define FRAME_recln216        	223
+#define FRAME_recln217        	224
+#define FRAME_recln218        	225
+#define FRAME_recln219        	226
+#define FRAME_recln220        	227
+#define FRAME_recln221        	228
+#define FRAME_recln222        	229
+#define FRAME_recln223        	230
+#define FRAME_recln224        	231
+#define FRAME_recln225        	232
+#define FRAME_recln226        	233
+#define FRAME_recln227        	234
+#define FRAME_recln228        	235
+#define FRAME_recln229        	236
+#define FRAME_recln230        	237
+#define FRAME_recln231        	238
+#define FRAME_recln232        	239
+#define FRAME_recln233        	240
+#define FRAME_recln234        	241
+#define FRAME_recln235        	242
+#define FRAME_recln236        	243
+#define FRAME_recln237        	244
+#define FRAME_recln238        	245
+#define FRAME_recln239        	246
+#define FRAME_recln240        	247
+#define FRAME_recln101        	248
+#define FRAME_recln102        	249
+#define FRAME_recln103        	250
+#define FRAME_recln104        	251
+#define FRAME_recln105        	252
+#define FRAME_recln106        	253
+#define FRAME_recln107        	254
+#define FRAME_recln108        	255
+#define FRAME_recln109        	256
+#define FRAME_recln110        	257
+#define FRAME_recln111        	258
+#define FRAME_recln112        	259
+#define FRAME_recln113        	260
+#define FRAME_recln114        	261
+#define FRAME_recln115        	262
+#define FRAME_recln116        	263
+#define FRAME_recln117        	264
+#define FRAME_recln118        	265
+#define FRAME_recln119        	266
+#define FRAME_recln120        	267
+#define FRAME_recln121        	268
+#define FRAME_recln122        	269
+#define FRAME_recln123        	270
+#define FRAME_recln124        	271
+#define FRAME_recln125        	272
+#define FRAME_recln126        	273
+#define FRAME_recln127        	274
+#define FRAME_recln128        	275
+#define FRAME_recln129        	276
+#define FRAME_recln130        	277
+#define FRAME_recln131        	278
+#define FRAME_recln132        	279
+#define FRAME_recln133        	280
+#define FRAME_recln134        	281
+#define FRAME_recln135        	282
+#define FRAME_recln136        	283
+#define FRAME_recln137        	284
+#define FRAME_recln138        	285
+#define FRAME_recln139        	286
+#define FRAME_recln140        	287
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_flash.c
@@ -1,0 +1,471 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+// this file is included in both the game dll and quake2,
+// the game needs it to source shot locations, the client
+// needs it to position muzzle flashes
+vec3_t monster_flash_offset [] =
+{
+// flash 0 is not used
+	0.0, 0.0, 0.0,
+
+// MZ2_TANK_BLASTER_1				1
+	20.7, -18.5, 28.7,
+// MZ2_TANK_BLASTER_2				2
+	16.6, -21.5, 30.1,
+// MZ2_TANK_BLASTER_3				3
+	11.8, -23.9, 32.1,
+// MZ2_TANK_MACHINEGUN_1			4
+	22.9, -0.7, 25.3,
+// MZ2_TANK_MACHINEGUN_2			5
+	22.2, 6.2, 22.3,
+// MZ2_TANK_MACHINEGUN_3			6
+	19.4, 13.1, 18.6,
+// MZ2_TANK_MACHINEGUN_4			7
+	19.4, 18.8, 18.6,
+// MZ2_TANK_MACHINEGUN_5			8
+	17.9, 25.0, 18.6,
+// MZ2_TANK_MACHINEGUN_6			9
+	14.1, 30.5, 20.6,
+// MZ2_TANK_MACHINEGUN_7			10
+	9.3, 35.3, 22.1,
+// MZ2_TANK_MACHINEGUN_8			11
+	4.7, 38.4, 22.1,
+// MZ2_TANK_MACHINEGUN_9			12
+	-1.1, 40.4, 24.1,
+// MZ2_TANK_MACHINEGUN_10			13
+	-6.5, 41.2, 24.1,
+// MZ2_TANK_MACHINEGUN_11			14
+	3.2, 40.1, 24.7,
+// MZ2_TANK_MACHINEGUN_12			15
+	11.7, 36.7, 26.0,
+// MZ2_TANK_MACHINEGUN_13			16
+	18.9, 31.3, 26.0,
+// MZ2_TANK_MACHINEGUN_14			17
+	24.4, 24.4, 26.4,
+// MZ2_TANK_MACHINEGUN_15			18
+	27.1, 17.1, 27.2,
+// MZ2_TANK_MACHINEGUN_16			19
+	28.5, 9.1, 28.0,
+// MZ2_TANK_MACHINEGUN_17			20
+	27.1, 2.2, 28.0,
+// MZ2_TANK_MACHINEGUN_18			21
+	24.9, -2.8, 28.0,
+// MZ2_TANK_MACHINEGUN_19			22
+	21.6, -7.0, 26.4,
+// MZ2_TANK_ROCKET_1				23
+	6.2, 29.1, 49.1,
+// MZ2_TANK_ROCKET_2				24
+	6.9, 23.8, 49.1,
+// MZ2_TANK_ROCKET_3				25
+	8.3, 17.8, 49.5,
+
+// MZ2_INFANTRY_MACHINEGUN_1		26
+	26.6, 7.1, 13.1,
+// MZ2_INFANTRY_MACHINEGUN_2		27
+	18.2, 7.5, 15.4,
+// MZ2_INFANTRY_MACHINEGUN_3		28
+	17.2, 10.3, 17.9,
+// MZ2_INFANTRY_MACHINEGUN_4		29
+	17.0, 12.8, 20.1,
+// MZ2_INFANTRY_MACHINEGUN_5		30
+	15.1, 14.1, 21.8,
+// MZ2_INFANTRY_MACHINEGUN_6		31
+	11.8, 17.2, 23.1,
+// MZ2_INFANTRY_MACHINEGUN_7		32
+	11.4, 20.2, 21.0,
+// MZ2_INFANTRY_MACHINEGUN_8		33
+	9.0, 23.0, 18.9,
+// MZ2_INFANTRY_MACHINEGUN_9		34
+	13.9, 18.6, 17.7,
+// MZ2_INFANTRY_MACHINEGUN_10		35
+	15.4, 15.6, 15.8,
+// MZ2_INFANTRY_MACHINEGUN_11		36
+	10.2, 15.2, 25.1,
+// MZ2_INFANTRY_MACHINEGUN_12		37
+	-1.9, 15.1, 28.2,
+// MZ2_INFANTRY_MACHINEGUN_13		38
+	-12.4, 13.0, 20.2,
+
+// MZ2_SOLDIER_BLASTER_1			39
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_2			40
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_1			41
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_2			42
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_1			43
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_2			44
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+
+// MZ2_GUNNER_MACHINEGUN_1			45
+	30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_2			46
+	29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_3			47
+	28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_4			48
+	28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_5			49
+	26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_6			50
+	26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_7			51
+	26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_8			52
+	29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15,
+// MZ2_GUNNER_GRENADE_1				53
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_2				54
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_3				55
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_4				56
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+
+// MZ2_CHICK_ROCKET_1				57
+//	-24.8, -9.0, 39.0,
+	24.8, -9.0, 39.0,			// PGM - this was incorrect in Q2
+
+// MZ2_FLYER_BLASTER_1				58
+	12.1, 13.4, -14.5,
+// MZ2_FLYER_BLASTER_2				59
+	12.1, -7.4, -14.5,
+
+// MZ2_MEDIC_BLASTER_1				60
+	12.1, 5.4, 16.5,
+
+// MZ2_GLADIATOR_RAILGUN_1			61
+	30.0, 18.0, 28.0,
+
+// MZ2_HOVER_BLASTER_1				62
+	32.5, -0.8, 10.0,
+
+// MZ2_ACTOR_MACHINEGUN_1			63
+	18.4, 7.4, 9.6,
+
+// MZ2_SUPERTANK_MACHINEGUN_1		64
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_2		65
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_3		66
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_4		67
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_5		68
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_6		69
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_ROCKET_1			70
+	16.0, -22.5, 91.2,
+// MZ2_SUPERTANK_ROCKET_2			71
+	16.0, -33.4, 86.7,
+// MZ2_SUPERTANK_ROCKET_3			72
+	16.0, -42.8, 83.3,
+
+// --- Start Xian Stuff ---
+// MZ2_BOSS2_MACHINEGUN_L1			73
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L2			74
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L3			75
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L4			76
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L5			77
+	32,	-40,	70,
+// --- End Xian Stuff
+
+// MZ2_BOSS2_ROCKET_1				78
+	22.0, 16.0, 10.0,
+// MZ2_BOSS2_ROCKET_2				79
+	22.0, 8.0, 10.0,
+// MZ2_BOSS2_ROCKET_3				80
+	22.0, -8.0, 10.0,
+// MZ2_BOSS2_ROCKET_4				81
+	22.0, -16.0, 10.0,
+
+// MZ2_FLOAT_BLASTER_1				82
+	32.5, -0.8, 10,
+
+// MZ2_SOLDIER_BLASTER_3			83
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_3			84
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_3			85
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_4			86
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_4			87
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_4			88
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_5			89
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_5			90
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_5			91
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_6			92
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_6			93
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_6			94
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_BLASTER_7			95
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_7			96
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_7			97
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_BLASTER_8			98
+//	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+	31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_8			99
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_8			100
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+
+// --- Xian shit below ---
+// MZ2_MAKRON_BFG					101
+	17,		-19.5,	62.9,
+// MZ2_MAKRON_BLASTER_1				102
+	-3.6,	-24.1,	59.5,
+// MZ2_MAKRON_BLASTER_2				103
+	-1.6,	-19.3,	59.5,
+// MZ2_MAKRON_BLASTER_3				104
+	-0.1,	-14.4,	59.5,		
+// MZ2_MAKRON_BLASTER_4				105
+	2.0,	-7.6,	59.5,	
+// MZ2_MAKRON_BLASTER_5				106
+	3.4,	1.3,	59.5,
+// MZ2_MAKRON_BLASTER_6				107
+	3.7,	11.1,	59.5,	
+// MZ2_MAKRON_BLASTER_7				108
+	-0.3,	22.3,	59.5,
+// MZ2_MAKRON_BLASTER_8				109
+	-6,		33,		59.5,
+// MZ2_MAKRON_BLASTER_9				110
+	-9.3,	36.4,	59.5,
+// MZ2_MAKRON_BLASTER_10			111
+	-7,		35,		59.5,
+// MZ2_MAKRON_BLASTER_11			112
+	-2.1,	29,		59.5,
+// MZ2_MAKRON_BLASTER_12			113
+	3.9,	17.3,	59.5,
+// MZ2_MAKRON_BLASTER_13			114
+	6.1,	5.8,	59.5,
+// MZ2_MAKRON_BLASTER_14			115
+	5.9,	-4.4,	59.5,
+// MZ2_MAKRON_BLASTER_15			116
+	4.2,	-14.1,	59.5,		
+// MZ2_MAKRON_BLASTER_16			117
+	2.4,	-18.8,	59.5,
+// MZ2_MAKRON_BLASTER_17			118
+	-1.8,	-25.5,	59.5,
+// MZ2_MAKRON_RAILGUN_1				119
+	-17.3,	7.8,	72.4,
+
+// MZ2_JORG_MACHINEGUN_L1			120
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L2			121
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L3			122
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L4			123
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L5			124
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L6			125
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_R1			126
+	78.5,	46.7,  96,			
+// MZ2_JORG_MACHINEGUN_R2			127
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R3			128
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R4			129
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R5			130
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R6			131
+	78.5,	46.7,	96,			
+// MZ2_JORG_BFG_1					132
+	6.3,	-9,		111.2,
+
+// MZ2_BOSS2_MACHINEGUN_R1			73
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R2			74
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R3			75
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R4			76
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R5			77
+	32,	40,	70,
+
+// --- End Xian Shit ---
+
+// ROGUE
+// note that the above really ends at 137
+// carrier machineguns
+// MZ2_CARRIER_MACHINEGUN_L1
+	56,	-32, 32,
+// MZ2_CARRIER_MACHINEGUN_R1
+	56,	32, 32,
+// MZ2_CARRIER_GRENADE
+	42,	24, 50,
+// MZ2_TURRET_MACHINEGUN			141
+	16, 0, 0,
+// MZ2_TURRET_ROCKET				142
+	16, 0, 0,
+// MZ2_TURRET_BLASTER				143
+	16, 0, 0,
+// MZ2_STALKER_BLASTER				144
+	24, 0, 6,
+// MZ2_DAEDALUS_BLASTER				145
+	32.5, -0.8, 10.0,
+// MZ2_MEDIC_BLASTER_2				146
+	12.1, 5.4, 16.5,
+// MZ2_CARRIER_RAILGUN				147
+	32, 0, 6, 
+// MZ2_WIDOW_DISRUPTOR				148
+	57.72, 14.50, 88.81,
+// MZ2_WIDOW_BLASTER				149
+	56,	32, 32,
+// MZ2_WIDOW_RAIL					150
+	62, -20, 84, 
+// MZ2_WIDOW_PLASMABEAM				151		// PMM - not used!
+	32, 0, 6, 
+// MZ2_CARRIER_MACHINEGUN_L2		152
+	61,	-32, 12,
+// MZ2_CARRIER_MACHINEGUN_R2		153
+	61,	32, 12,
+// MZ2_WIDOW_RAIL_LEFT				154
+	17, -62, 91, 
+// MZ2_WIDOW_RAIL_RIGHT				155
+	68, 12, 86, 
+// MZ2_WIDOW_BLASTER_SWEEP1			156			pmm - the sweeps need to be in sequential order
+	47.5, 56, 89,
+// MZ2_WIDOW_BLASTER_SWEEP2			157
+	54, 52, 91,
+// MZ2_WIDOW_BLASTER_SWEEP3			158
+	58, 40, 91,
+// MZ2_WIDOW_BLASTER_SWEEP4			159
+	68, 30, 88,
+// MZ2_WIDOW_BLASTER_SWEEP5			160
+	74, 20, 88,
+// MZ2_WIDOW_BLASTER_SWEEP6			161
+	73, 11, 87,
+// MZ2_WIDOW_BLASTER_SWEEP7			162
+	73, 3, 87,
+// MZ2_WIDOW_BLASTER_SWEEP8			163
+	70, -12, 87,
+// MZ2_WIDOW_BLASTER_SWEEP9			164
+	67, -20, 90,
+// MZ2_WIDOW_BLASTER_100			165
+	-20, 76, 90,
+// MZ2_WIDOW_BLASTER_90				166
+	-8, 74, 90,
+// MZ2_WIDOW_BLASTER_80				167
+	0, 72, 90,
+// MZ2_WIDOW_BLASTER_70				168		d06
+	10, 71, 89,
+// MZ2_WIDOW_BLASTER_60				169		d07
+	23, 70, 87,
+// MZ2_WIDOW_BLASTER_50				170		d08
+	32, 64, 85,
+// MZ2_WIDOW_BLASTER_40				171
+	40, 58, 84,
+// MZ2_WIDOW_BLASTER_30				172		d10
+	48, 50, 83,
+// MZ2_WIDOW_BLASTER_20				173
+	54, 42, 82,
+// MZ2_WIDOW_BLASTER_10				174		d12
+	56, 34, 82,
+// MZ2_WIDOW_BLASTER_0				175
+	58, 26, 82,
+// MZ2_WIDOW_BLASTER_10L			176		d14
+	60, 16, 82,
+// MZ2_WIDOW_BLASTER_20L			177
+	59, 6, 81,
+// MZ2_WIDOW_BLASTER_30L			178		d16
+	58, -2, 80,
+// MZ2_WIDOW_BLASTER_40L			179
+	57, -10, 79,
+// MZ2_WIDOW_BLASTER_50L			180		d18
+	54, -18, 78,
+// MZ2_WIDOW_BLASTER_60L			181
+	42, -32, 80,
+// MZ2_WIDOW_BLASTER_70L			182		d20
+	36, -40, 78,
+// MZ2_WIDOW_RUN_1					183
+	68.4, 10.88, 82.08,
+// MZ2_WIDOW_RUN_2					184
+	68.51, 8.64, 85.14,
+// MZ2_WIDOW_RUN_3					185
+	68.66, 6.38, 88.78,
+// MZ2_WIDOW_RUN_4					186
+	68.73, 5.1, 84.47,
+// MZ2_WIDOW_RUN_5					187
+	68.82, 4.79, 80.52,
+// MZ2_WIDOW_RUN_6					188
+	68.77, 6.11, 85.37,
+// MZ2_WIDOW_RUN_7					189
+	68.67, 7.99, 90.24,
+// MZ2_WIDOW_RUN_8					190
+	68.55, 9.54, 87.36,
+// MZ2_CARRIER_ROCKET_1				191
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_2				192
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_3				193
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_4				194
+	0, 0, -5,
+// MZ2_WIDOW2_BEAMER_1				195
+//	72.13, -17.63, 93.77,
+	69.00, -17.63, 93.77,
+// MZ2_WIDOW2_BEAMER_2				196
+//	71.46, -17.08, 89.82,
+	69.00, -17.08, 89.82,
+// MZ2_WIDOW2_BEAMER_3				197
+//	71.47, -18.40, 90.70,
+	69.00, -18.40, 90.70,
+// MZ2_WIDOW2_BEAMER_4				198
+//	71.96, -18.34, 94.32,
+	69.00, -18.34, 94.32,
+// MZ2_WIDOW2_BEAMER_5				199
+//	72.25, -18.30, 97.98,
+	69.00, -18.30, 97.98,
+// MZ2_WIDOW2_BEAM_SWEEP_1			200
+	45.04, -59.02, 92.24,
+// MZ2_WIDOW2_BEAM_SWEEP_2			201
+	50.68, -54.70, 91.96,
+// MZ2_WIDOW2_BEAM_SWEEP_3			202
+	56.57, -47.72, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_4			203
+	61.75, -38.75, 91.38,
+// MZ2_WIDOW2_BEAM_SWEEP_5			204
+	65.55, -28.76, 91.24,
+// MZ2_WIDOW2_BEAM_SWEEP_6			205
+	67.79, -18.90, 91.22,
+// MZ2_WIDOW2_BEAM_SWEEP_7			206
+	68.60, -9.52, 91.23,
+// MZ2_WIDOW2_BEAM_SWEEP_8			207
+	68.08, 0.18, 91.32,
+// MZ2_WIDOW2_BEAM_SWEEP_9			208
+	66.14, 9.79, 91.44,
+// MZ2_WIDOW2_BEAM_SWEEP_10			209
+	62.77, 18.91, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_11			210
+	58.29, 27.11, 92.00,
+
+// end of table
+	0.0, 0.0, 0.0
+};
--- /dev/null
+++ b/rogue/m_flipper.c
@@ -1,0 +1,380 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_flipper.h"
+
+
+static int	sound_chomp;
+static int	sound_attack;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_search;
+static int	sound_sight;
+
+
+void flipper_stand (edict_t *self);
+
+mframe_t flipper_frames_stand [] =
+{
+	ai_stand, 0, NULL
+};
+	
+mmove_t	flipper_move_stand = {FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, NULL};
+
+void flipper_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flipper_move_stand;
+}
+
+#define FLIPPER_RUN_SPEED	24
+
+mframe_t flipper_frames_run [] =
+{
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 6
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 10
+
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 20
+
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL		// 29
+};
+mmove_t flipper_move_run_loop = {FRAME_flpver06, FRAME_flpver29, flipper_frames_run, NULL};
+
+void flipper_run_loop (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_run_loop;
+}
+
+mframe_t flipper_frames_run_start [] =
+{
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL
+};
+mmove_t flipper_move_run_start = {FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop};
+
+void flipper_run (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_run_start;
+}
+
+/* Standard Swimming */ 
+mframe_t flipper_frames_walk [] =
+{
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL
+};
+mmove_t flipper_move_walk = {FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, NULL};
+
+void flipper_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_walk;
+}
+
+mframe_t flipper_frames_start_run [] =
+{
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, flipper_run
+};
+mmove_t flipper_move_start_run = {FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, NULL};
+
+void flipper_start_run (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_start_run;
+}
+
+mframe_t flipper_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0, NULL
+};
+mmove_t flipper_move_pain2 = {FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run};
+
+mframe_t flipper_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0, NULL
+};
+mmove_t flipper_move_pain1 = {FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run};
+
+void flipper_bite (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	fire_hit (self, aim, 5, 0);
+}
+
+void flipper_preattack (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0);
+}
+
+mframe_t flipper_frames_attack [] =
+{
+	ai_charge, 0,	flipper_preattack,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	flipper_bite,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	flipper_bite,
+	ai_charge, 0,	NULL
+};
+mmove_t flipper_move_attack = {FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run};
+
+void flipper_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_attack;
+}
+
+void flipper_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = (rand() + 1) % 2;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flipper_move_pain1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flipper_move_pain2;
+	}
+}
+
+void flipper_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t flipper_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t flipper_move_death = {FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead};
+
+void flipper_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void flipper_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &flipper_move_death;
+}
+
+/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_flipper (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1		= gi.soundindex ("flipper/flppain1.wav");	
+	sound_pain2		= gi.soundindex ("flipper/flppain2.wav");	
+	sound_death		= gi.soundindex ("flipper/flpdeth1.wav");	
+	sound_chomp		= gi.soundindex ("flipper/flpatck1.wav");
+	sound_attack	= gi.soundindex ("flipper/flpatck2.wav");
+	sound_idle		= gi.soundindex ("flipper/flpidle1.wav");
+	sound_search	= gi.soundindex ("flipper/flpsrch1.wav");
+	sound_sight		= gi.soundindex ("flipper/flpsght1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/flipper/tris.md2");
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 50;
+	self->gib_health = -30;
+	self->mass = 100;
+
+	self->pain = flipper_pain;
+	self->die = flipper_die;
+
+	self->monsterinfo.stand = flipper_stand;
+	self->monsterinfo.walk = flipper_walk;
+	self->monsterinfo.run = flipper_start_run;
+	self->monsterinfo.melee = flipper_melee;
+	self->monsterinfo.sight = flipper_sight;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &flipper_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	swimmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_flipper.h
@@ -1,0 +1,166 @@
+// G:\quake2\baseq2\models/monsters/flipper
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_flpbit01        	0
+#define FRAME_flpbit02        	1
+#define FRAME_flpbit03        	2
+#define FRAME_flpbit04        	3
+#define FRAME_flpbit05        	4
+#define FRAME_flpbit06        	5
+#define FRAME_flpbit07        	6
+#define FRAME_flpbit08        	7
+#define FRAME_flpbit09        	8
+#define FRAME_flpbit10        	9
+#define FRAME_flpbit11        	10
+#define FRAME_flpbit12        	11
+#define FRAME_flpbit13        	12
+#define FRAME_flpbit14        	13
+#define FRAME_flpbit15        	14
+#define FRAME_flpbit16        	15
+#define FRAME_flpbit17        	16
+#define FRAME_flpbit18        	17
+#define FRAME_flpbit19        	18
+#define FRAME_flpbit20        	19
+#define FRAME_flptal01        	20
+#define FRAME_flptal02        	21
+#define FRAME_flptal03        	22
+#define FRAME_flptal04        	23
+#define FRAME_flptal05        	24
+#define FRAME_flptal06        	25
+#define FRAME_flptal07        	26
+#define FRAME_flptal08        	27
+#define FRAME_flptal09        	28
+#define FRAME_flptal10        	29
+#define FRAME_flptal11        	30
+#define FRAME_flptal12        	31
+#define FRAME_flptal13        	32
+#define FRAME_flptal14        	33
+#define FRAME_flptal15        	34
+#define FRAME_flptal16        	35
+#define FRAME_flptal17        	36
+#define FRAME_flptal18        	37
+#define FRAME_flptal19        	38
+#define FRAME_flptal20        	39
+#define FRAME_flptal21        	40
+#define FRAME_flphor01        	41
+#define FRAME_flphor02        	42
+#define FRAME_flphor03        	43
+#define FRAME_flphor04        	44
+#define FRAME_flphor05        	45
+#define FRAME_flphor06        	46
+#define FRAME_flphor07        	47
+#define FRAME_flphor08        	48
+#define FRAME_flphor09        	49
+#define FRAME_flphor10        	50
+#define FRAME_flphor11        	51
+#define FRAME_flphor12        	52
+#define FRAME_flphor13        	53
+#define FRAME_flphor14        	54
+#define FRAME_flphor15        	55
+#define FRAME_flphor16        	56
+#define FRAME_flphor17        	57
+#define FRAME_flphor18        	58
+#define FRAME_flphor19        	59
+#define FRAME_flphor20        	60
+#define FRAME_flphor21        	61
+#define FRAME_flphor22        	62
+#define FRAME_flphor23        	63
+#define FRAME_flphor24        	64
+#define FRAME_flpver01        	65
+#define FRAME_flpver02        	66
+#define FRAME_flpver03        	67
+#define FRAME_flpver04        	68
+#define FRAME_flpver05        	69
+#define FRAME_flpver06        	70
+#define FRAME_flpver07        	71
+#define FRAME_flpver08        	72
+#define FRAME_flpver09        	73
+#define FRAME_flpver10        	74
+#define FRAME_flpver11        	75
+#define FRAME_flpver12        	76
+#define FRAME_flpver13        	77
+#define FRAME_flpver14        	78
+#define FRAME_flpver15        	79
+#define FRAME_flpver16        	80
+#define FRAME_flpver17        	81
+#define FRAME_flpver18        	82
+#define FRAME_flpver19        	83
+#define FRAME_flpver20        	84
+#define FRAME_flpver21        	85
+#define FRAME_flpver22        	86
+#define FRAME_flpver23        	87
+#define FRAME_flpver24        	88
+#define FRAME_flpver25        	89
+#define FRAME_flpver26        	90
+#define FRAME_flpver27        	91
+#define FRAME_flpver28        	92
+#define FRAME_flpver29        	93
+#define FRAME_flppn101        	94
+#define FRAME_flppn102        	95
+#define FRAME_flppn103        	96
+#define FRAME_flppn104        	97
+#define FRAME_flppn105        	98
+#define FRAME_flppn201        	99
+#define FRAME_flppn202        	100
+#define FRAME_flppn203        	101
+#define FRAME_flppn204        	102
+#define FRAME_flppn205        	103
+#define FRAME_flpdth01        	104
+#define FRAME_flpdth02        	105
+#define FRAME_flpdth03        	106
+#define FRAME_flpdth04        	107
+#define FRAME_flpdth05        	108
+#define FRAME_flpdth06        	109
+#define FRAME_flpdth07        	110
+#define FRAME_flpdth08        	111
+#define FRAME_flpdth09        	112
+#define FRAME_flpdth10        	113
+#define FRAME_flpdth11        	114
+#define FRAME_flpdth12        	115
+#define FRAME_flpdth13        	116
+#define FRAME_flpdth14        	117
+#define FRAME_flpdth15        	118
+#define FRAME_flpdth16        	119
+#define FRAME_flpdth17        	120
+#define FRAME_flpdth18        	121
+#define FRAME_flpdth19        	122
+#define FRAME_flpdth20        	123
+#define FRAME_flpdth21        	124
+#define FRAME_flpdth22        	125
+#define FRAME_flpdth23        	126
+#define FRAME_flpdth24        	127
+#define FRAME_flpdth25        	128
+#define FRAME_flpdth26        	129
+#define FRAME_flpdth27        	130
+#define FRAME_flpdth28        	131
+#define FRAME_flpdth29        	132
+#define FRAME_flpdth30        	133
+#define FRAME_flpdth31        	134
+#define FRAME_flpdth32        	135
+#define FRAME_flpdth33        	136
+#define FRAME_flpdth34        	137
+#define FRAME_flpdth35        	138
+#define FRAME_flpdth36        	139
+#define FRAME_flpdth37        	140
+#define FRAME_flpdth38        	141
+#define FRAME_flpdth39        	142
+#define FRAME_flpdth40        	143
+#define FRAME_flpdth41        	144
+#define FRAME_flpdth42        	145
+#define FRAME_flpdth43        	146
+#define FRAME_flpdth44        	147
+#define FRAME_flpdth45        	148
+#define FRAME_flpdth46        	149
+#define FRAME_flpdth47        	150
+#define FRAME_flpdth48        	151
+#define FRAME_flpdth49        	152
+#define FRAME_flpdth50        	153
+#define FRAME_flpdth51        	154
+#define FRAME_flpdth52        	155
+#define FRAME_flpdth53        	156
+#define FRAME_flpdth54        	157
+#define FRAME_flpdth55        	158
+#define FRAME_flpdth56        	159
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_float.c
@@ -1,0 +1,700 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_float.h"
+
+
+static int	sound_attack2;
+static int	sound_attack3;
+static int	sound_death1;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+
+
+void floater_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void floater_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//void floater_stand1 (edict_t *self);
+void floater_dead (edict_t *self);
+void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void floater_run (edict_t *self);
+void floater_wham (edict_t *self);
+void floater_zap (edict_t *self);
+
+
+void floater_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect);
+}
+
+
+mframe_t floater_frames_stand1 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	floater_move_stand1 = {FRAME_stand101, FRAME_stand152, floater_frames_stand1, NULL};
+
+mframe_t floater_frames_stand2 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	floater_move_stand2 = {FRAME_stand201, FRAME_stand252, floater_frames_stand2, NULL};
+
+void floater_stand (edict_t *self)
+{
+	if (qrandom() <= 0.5)		
+		self->monsterinfo.currentmove = &floater_move_stand1;
+	else
+		self->monsterinfo.currentmove = &floater_move_stand2;
+}
+
+mframe_t floater_frames_activate [] =
+{
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_activate, NULL};
+
+mframe_t floater_frames_attack1 [] =
+{
+	ai_charge,	0,	NULL,			// Blaster attack
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_fire_blaster,			// BOOM (0, -25.8, 32.5)	-- LOOP Starts
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL			//							-- LOOP Ends
+};
+mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run};
+
+// PMM - circle strafe frames
+mframe_t floater_frames_attack1a [] =
+{
+	ai_charge,	10,	NULL,			// Blaster attack
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	floater_fire_blaster,			// BOOM (0, -25.8, 32.5)	-- LOOP Starts
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	floater_fire_blaster,
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	NULL			//							-- LOOP Ends
+};
+mmove_t floater_move_attack1a = {FRAME_attak101, FRAME_attak114, floater_frames_attack1a, floater_run};
+//pmm
+mframe_t floater_frames_attack2 [] =
+{
+	ai_charge,	0,	NULL,			// Claws
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_wham,			// WHAM (0, -45, 29.6)		-- LOOP Starts
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,			//							-- LOOP Ends
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t floater_move_attack2 = {FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run};
+
+mframe_t floater_frames_attack3 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_zap,		//								-- LOOP Starts
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,		//								-- LOOP Ends
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t floater_move_attack3 = {FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run};
+
+mframe_t floater_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_death = {FRAME_death01, FRAME_death13, floater_frames_death, floater_dead};
+
+mframe_t floater_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain1 = {FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run};
+
+mframe_t floater_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain2 = {FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run};
+
+mframe_t floater_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain3 = {FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run};
+
+mframe_t floater_frames_walk [] =
+{
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL
+};
+mmove_t	floater_move_walk = {FRAME_stand101, FRAME_stand152, floater_frames_walk, NULL};
+
+mframe_t floater_frames_run [] =
+{
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL
+};
+mmove_t	floater_move_run = {FRAME_stand101, FRAME_stand152, floater_frames_run, NULL};
+
+void floater_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &floater_move_stand1;
+	else
+		self->monsterinfo.currentmove = &floater_move_run;
+}
+
+void floater_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &floater_move_walk;
+}
+
+void floater_wham (edict_t *self)
+{
+	static	vec3_t	aim = {MELEE_DISTANCE, 0, 0};
+	gi.sound (self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0);
+	fire_hit (self, aim, 5 + rand() % 6, -50);
+}
+
+void floater_zap (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	origin;
+	vec3_t	dir;
+	vec3_t	offset;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	//FIXME use a flash and replace these two lines with the commented one
+	VectorSet (offset, 18.5, -0.9, 10);
+	G_ProjectSource (self->s.origin, offset, forward, right, origin);
+//	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, origin);
+
+	gi.sound (self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0);
+
+	//FIXME use the flash, Luke
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPLASH);
+	gi.WriteByte (32);
+	gi.WritePosition (origin);
+	gi.WriteDir (dir);
+	gi.WriteByte (1);	//sparks
+	gi.multicast (origin, MULTICAST_PVS);
+
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN);
+}
+
+void floater_attack(edict_t *self)
+{
+	float chance;
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	// 0% chance of circle in easy
+	// 50% chance in normal
+	// 75% chance in hard
+	// 86.67% chance in nightmare
+	if (!skill->value)
+		chance = 0;
+	else
+		chance = 1.0 - (0.5/(float)(skill->value));
+
+	if (qrandom() > chance)
+	{
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+		self->monsterinfo.currentmove = &floater_move_attack1;
+	}
+	else // circle strafe
+	{
+		if (qrandom () <= 0.5) // switch directions
+			self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		self->monsterinfo.currentmove = &floater_move_attack1a;
+	}
+}
+
+
+void floater_melee(edict_t *self)
+{
+	if (qrandom() < 0.5)		
+		self->monsterinfo.currentmove = &floater_move_attack3;
+	else
+		self->monsterinfo.currentmove = &floater_move_attack2;
+}
+
+
+void floater_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = (rand() + 1) % 3;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &floater_move_pain1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &floater_move_pain2;
+	}
+}
+
+void floater_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void floater_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+	BecomeExplosion1(self);
+}
+
+//===========
+//PGM
+qboolean floater_blocked (edict_t *self, float)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_floater (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_attack2 = gi.soundindex ("floater/fltatck2.wav");
+	sound_attack3 = gi.soundindex ("floater/fltatck3.wav");
+	sound_death1 = gi.soundindex ("floater/fltdeth1.wav");
+	sound_idle = gi.soundindex ("floater/fltidle1.wav");
+	sound_pain1 = gi.soundindex ("floater/fltpain1.wav");
+	sound_pain2 = gi.soundindex ("floater/fltpain2.wav");
+	sound_sight = gi.soundindex ("floater/fltsght1.wav");
+
+	gi.soundindex ("floater/fltatck1.wav");
+
+	self->s.sound = gi.soundindex ("floater/fltsrch1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/float/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+	self->health = 200;
+	self->gib_health = -80;
+	self->mass = 300;
+
+	self->pain = floater_pain;
+	self->die = floater_die;
+
+	self->monsterinfo.stand = floater_stand;
+	self->monsterinfo.walk = floater_walk;
+	self->monsterinfo.run = floater_run;
+//	self->monsterinfo.dodge = floater_dodge;
+	self->monsterinfo.attack = floater_attack;
+	self->monsterinfo.melee = floater_melee;
+	self->monsterinfo.sight = floater_sight;
+	self->monsterinfo.idle = floater_idle;
+	self->monsterinfo.blocked = floater_blocked;		// PGM
+
+	gi.linkentity (self);
+
+	if (qrandom() <= 0.5)		
+		self->monsterinfo.currentmove = &floater_move_stand1;	
+	else
+		self->monsterinfo.currentmove = &floater_move_stand2;	
+	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_float.h
@@ -1,0 +1,254 @@
+// G:\quake2\baseq2\models/monsters/float
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_actvat01        	0
+#define FRAME_actvat02        	1
+#define FRAME_actvat03        	2
+#define FRAME_actvat04        	3
+#define FRAME_actvat05        	4
+#define FRAME_actvat06        	5
+#define FRAME_actvat07        	6
+#define FRAME_actvat08        	7
+#define FRAME_actvat09        	8
+#define FRAME_actvat10        	9
+#define FRAME_actvat11        	10
+#define FRAME_actvat12        	11
+#define FRAME_actvat13        	12
+#define FRAME_actvat14        	13
+#define FRAME_actvat15        	14
+#define FRAME_actvat16        	15
+#define FRAME_actvat17        	16
+#define FRAME_actvat18        	17
+#define FRAME_actvat19        	18
+#define FRAME_actvat20        	19
+#define FRAME_actvat21        	20
+#define FRAME_actvat22        	21
+#define FRAME_actvat23        	22
+#define FRAME_actvat24        	23
+#define FRAME_actvat25        	24
+#define FRAME_actvat26        	25
+#define FRAME_actvat27        	26
+#define FRAME_actvat28        	27
+#define FRAME_actvat29        	28
+#define FRAME_actvat30        	29
+#define FRAME_actvat31        	30
+#define FRAME_attak101        	31
+#define FRAME_attak102        	32
+#define FRAME_attak103        	33
+#define FRAME_attak104        	34
+#define FRAME_attak105        	35
+#define FRAME_attak106        	36
+#define FRAME_attak107        	37
+#define FRAME_attak108        	38
+#define FRAME_attak109        	39
+#define FRAME_attak110        	40
+#define FRAME_attak111        	41
+#define FRAME_attak112        	42
+#define FRAME_attak113        	43
+#define FRAME_attak114        	44
+#define FRAME_attak201        	45
+#define FRAME_attak202        	46
+#define FRAME_attak203        	47
+#define FRAME_attak204        	48
+#define FRAME_attak205        	49
+#define FRAME_attak206        	50
+#define FRAME_attak207        	51
+#define FRAME_attak208        	52
+#define FRAME_attak209        	53
+#define FRAME_attak210        	54
+#define FRAME_attak211        	55
+#define FRAME_attak212        	56
+#define FRAME_attak213        	57
+#define FRAME_attak214        	58
+#define FRAME_attak215        	59
+#define FRAME_attak216        	60
+#define FRAME_attak217        	61
+#define FRAME_attak218        	62
+#define FRAME_attak219        	63
+#define FRAME_attak220        	64
+#define FRAME_attak221        	65
+#define FRAME_attak222        	66
+#define FRAME_attak223        	67
+#define FRAME_attak224        	68
+#define FRAME_attak225        	69
+#define FRAME_attak301        	70
+#define FRAME_attak302        	71
+#define FRAME_attak303        	72
+#define FRAME_attak304        	73
+#define FRAME_attak305        	74
+#define FRAME_attak306        	75
+#define FRAME_attak307        	76
+#define FRAME_attak308        	77
+#define FRAME_attak309        	78
+#define FRAME_attak310        	79
+#define FRAME_attak311        	80
+#define FRAME_attak312        	81
+#define FRAME_attak313        	82
+#define FRAME_attak314        	83
+#define FRAME_attak315        	84
+#define FRAME_attak316        	85
+#define FRAME_attak317        	86
+#define FRAME_attak318        	87
+#define FRAME_attak319        	88
+#define FRAME_attak320        	89
+#define FRAME_attak321        	90
+#define FRAME_attak322        	91
+#define FRAME_attak323        	92
+#define FRAME_attak324        	93
+#define FRAME_attak325        	94
+#define FRAME_attak326        	95
+#define FRAME_attak327        	96
+#define FRAME_attak328        	97
+#define FRAME_attak329        	98
+#define FRAME_attak330        	99
+#define FRAME_attak331        	100
+#define FRAME_attak332        	101
+#define FRAME_attak333        	102
+#define FRAME_attak334        	103
+#define FRAME_death01         	104
+#define FRAME_death02         	105
+#define FRAME_death03         	106
+#define FRAME_death04         	107
+#define FRAME_death05         	108
+#define FRAME_death06         	109
+#define FRAME_death07         	110
+#define FRAME_death08         	111
+#define FRAME_death09         	112
+#define FRAME_death10         	113
+#define FRAME_death11         	114
+#define FRAME_death12         	115
+#define FRAME_death13         	116
+#define FRAME_pain101         	117
+#define FRAME_pain102         	118
+#define FRAME_pain103         	119
+#define FRAME_pain104         	120
+#define FRAME_pain105         	121
+#define FRAME_pain106         	122
+#define FRAME_pain107         	123
+#define FRAME_pain201         	124
+#define FRAME_pain202         	125
+#define FRAME_pain203         	126
+#define FRAME_pain204         	127
+#define FRAME_pain205         	128
+#define FRAME_pain206         	129
+#define FRAME_pain207         	130
+#define FRAME_pain208         	131
+#define FRAME_pain301         	132
+#define FRAME_pain302         	133
+#define FRAME_pain303         	134
+#define FRAME_pain304         	135
+#define FRAME_pain305         	136
+#define FRAME_pain306         	137
+#define FRAME_pain307         	138
+#define FRAME_pain308         	139
+#define FRAME_pain309         	140
+#define FRAME_pain310         	141
+#define FRAME_pain311         	142
+#define FRAME_pain312         	143
+#define FRAME_stand101        	144
+#define FRAME_stand102        	145
+#define FRAME_stand103        	146
+#define FRAME_stand104        	147
+#define FRAME_stand105        	148
+#define FRAME_stand106        	149
+#define FRAME_stand107        	150
+#define FRAME_stand108        	151
+#define FRAME_stand109        	152
+#define FRAME_stand110        	153
+#define FRAME_stand111        	154
+#define FRAME_stand112        	155
+#define FRAME_stand113        	156
+#define FRAME_stand114        	157
+#define FRAME_stand115        	158
+#define FRAME_stand116        	159
+#define FRAME_stand117        	160
+#define FRAME_stand118        	161
+#define FRAME_stand119        	162
+#define FRAME_stand120        	163
+#define FRAME_stand121        	164
+#define FRAME_stand122        	165
+#define FRAME_stand123        	166
+#define FRAME_stand124        	167
+#define FRAME_stand125        	168
+#define FRAME_stand126        	169
+#define FRAME_stand127        	170
+#define FRAME_stand128        	171
+#define FRAME_stand129        	172
+#define FRAME_stand130        	173
+#define FRAME_stand131        	174
+#define FRAME_stand132        	175
+#define FRAME_stand133        	176
+#define FRAME_stand134        	177
+#define FRAME_stand135        	178
+#define FRAME_stand136        	179
+#define FRAME_stand137        	180
+#define FRAME_stand138        	181
+#define FRAME_stand139        	182
+#define FRAME_stand140        	183
+#define FRAME_stand141        	184
+#define FRAME_stand142        	185
+#define FRAME_stand143        	186
+#define FRAME_stand144        	187
+#define FRAME_stand145        	188
+#define FRAME_stand146        	189
+#define FRAME_stand147        	190
+#define FRAME_stand148        	191
+#define FRAME_stand149        	192
+#define FRAME_stand150        	193
+#define FRAME_stand151        	194
+#define FRAME_stand152        	195
+#define FRAME_stand201        	196
+#define FRAME_stand202        	197
+#define FRAME_stand203        	198
+#define FRAME_stand204        	199
+#define FRAME_stand205        	200
+#define FRAME_stand206        	201
+#define FRAME_stand207        	202
+#define FRAME_stand208        	203
+#define FRAME_stand209        	204
+#define FRAME_stand210        	205
+#define FRAME_stand211        	206
+#define FRAME_stand212        	207
+#define FRAME_stand213        	208
+#define FRAME_stand214        	209
+#define FRAME_stand215        	210
+#define FRAME_stand216        	211
+#define FRAME_stand217        	212
+#define FRAME_stand218        	213
+#define FRAME_stand219        	214
+#define FRAME_stand220        	215
+#define FRAME_stand221        	216
+#define FRAME_stand222        	217
+#define FRAME_stand223        	218
+#define FRAME_stand224        	219
+#define FRAME_stand225        	220
+#define FRAME_stand226        	221
+#define FRAME_stand227        	222
+#define FRAME_stand228        	223
+#define FRAME_stand229        	224
+#define FRAME_stand230        	225
+#define FRAME_stand231        	226
+#define FRAME_stand232        	227
+#define FRAME_stand233        	228
+#define FRAME_stand234        	229
+#define FRAME_stand235        	230
+#define FRAME_stand236        	231
+#define FRAME_stand237        	232
+#define FRAME_stand238        	233
+#define FRAME_stand239        	234
+#define FRAME_stand240        	235
+#define FRAME_stand241        	236
+#define FRAME_stand242        	237
+#define FRAME_stand243        	238
+#define FRAME_stand244        	239
+#define FRAME_stand245        	240
+#define FRAME_stand246        	241
+#define FRAME_stand247        	242
+#define FRAME_stand248        	243
+#define FRAME_stand249        	244
+#define FRAME_stand250        	245
+#define FRAME_stand251        	246
+#define FRAME_stand252        	247
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_flyer.c
@@ -1,0 +1,868 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_flyer.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	nextmove;			// Used for start/stop frames
+
+static int	sound_sight;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_slash;
+static int	sound_sproing;
+static int	sound_die;
+
+
+void flyer_check_melee(edict_t *self);
+void flyer_loop_melee (edict_t *self);
+void flyer_melee (edict_t *self);
+void flyer_setstart (edict_t *self);
+void flyer_stand (edict_t *self);
+void flyer_nextmove (edict_t *self);
+
+// ROGUE - kamikaze stuff
+void flyer_kamikaze (edict_t *self);
+void flyer_kamikaze_check (edict_t *self);
+void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+/*
+void showme1 (edict_t *self)
+{
+	if (!self->dmg)
+	{
+		gi.dprintf ("straight - %d\n", self->monsterinfo.lefty);
+		self->dmg = 1;
+	}
+}
+
+void showme2 (edict_t *self)
+{
+	if (!self->dmg)
+	{
+		gi.dprintf ("strafe - %d\n", self->monsterinfo.lefty);
+		self->dmg = 1;
+	}
+}
+*/
+
+void flyer_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void flyer_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void flyer_pop_blades (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t flyer_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	flyer_move_stand = {FRAME_stand01, FRAME_stand45, flyer_frames_stand, NULL};
+
+
+mframe_t flyer_frames_walk [] =
+{
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL
+};
+mmove_t	flyer_move_walk = {FRAME_stand01, FRAME_stand45, flyer_frames_walk, NULL};
+
+mframe_t flyer_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL
+};
+mmove_t	flyer_move_run = {FRAME_stand01, FRAME_stand45, flyer_frames_run, NULL};
+
+mframe_t flyer_frames_kamizake [] =
+{
+	ai_charge, 40,	flyer_kamikaze_check,
+	ai_charge, 40,	flyer_kamikaze_check,
+	ai_charge, 40,	flyer_kamikaze_check,
+	ai_charge, 40,	flyer_kamikaze_check,
+	ai_charge, 40,	flyer_kamikaze_check
+};
+mmove_t flyer_move_kamikaze = {FRAME_rollr02, FRAME_rollr06, flyer_frames_kamizake, flyer_kamikaze};
+
+void flyer_run (edict_t *self)
+{
+	if (self->mass > 50)
+		self->monsterinfo.currentmove = &flyer_move_kamikaze;
+	else
+		if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+			self->monsterinfo.currentmove = &flyer_move_stand;
+		else
+			self->monsterinfo.currentmove = &flyer_move_run;
+}
+
+void flyer_walk (edict_t *self)
+{
+	if (self->mass > 50)
+		flyer_run (self);
+	else
+		self->monsterinfo.currentmove = &flyer_move_walk;
+}
+
+void flyer_stand (edict_t *self)
+{
+	if (self->mass > 50)
+		flyer_run (self);
+	else
+		self->monsterinfo.currentmove = &flyer_move_stand;
+}
+
+// ROGUE - kamikaze stuff
+
+void flyer_kamikaze_explode (edict_t *self)
+{
+	vec3_t dir;
+
+	if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && 
+		!strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
+	{
+		self->monsterinfo.commander->monsterinfo.monster_slots++;
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("suicide hit!. %d slots left\n", self->monsterinfo.commander->monsterinfo.monster_slots);
+	}
+
+//	gi.dprintf ("boom!\n");
+//	T_RadiusDamage(self, self->owner, 125, self, self->dmg_radius, MOD_NUKE);
+//	T_RadiusDamage(self, self->owner, 125, self, 150, MOD_NUKE);
+	if (self->enemy)
+	{
+		VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+//void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, 
+//			   vec3_t normal, int damage, int knockback, int dflags, int mod)
+		T_Damage (self->enemy, self, self, dir, self->s.origin, vec3_origin, (int)50, (int)50, DAMAGE_RADIUS, MOD_UNKNOWN);
+	}
+
+	flyer_die (self, NULL, NULL, 0, dir);
+/*	VectorMA (self->s.origin, -0.02, self->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (self);
+*/
+}
+
+void flyer_kamikaze (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flyer_move_kamikaze;
+}
+
+void flyer_kamikaze_check (edict_t *self)
+{
+	float	dist;
+
+	// PMM - this needed because we could have gone away before we get here (blocked code)
+	if (!self->inuse)
+		return;
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		flyer_kamikaze_explode (self);
+		return;
+	}
+
+	self->goalentity = self->enemy;
+
+	dist = realrange (self, self->enemy);
+
+	if (dist < 90)
+		flyer_kamikaze_explode (self);
+}
+
+// rogue - kamikaze
+
+mframe_t flyer_frames_start [] =
+{
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	flyer_nextmove
+};
+mmove_t flyer_move_start = {FRAME_start01, FRAME_start06, flyer_frames_start, NULL};
+
+mframe_t flyer_frames_stop [] =
+{
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	flyer_nextmove
+};
+mmove_t flyer_move_stop = {FRAME_stop01, FRAME_stop07, flyer_frames_stop, NULL};
+
+void flyer_stop (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flyer_move_stop;
+}
+
+void flyer_start (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+
+mframe_t flyer_frames_rollright [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_rollright = {FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, NULL};
+
+mframe_t flyer_frames_rollleft [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_rollleft = {FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, NULL};
+
+mframe_t flyer_frames_pain3 [] =
+{	
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain3 = {FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run};
+
+mframe_t flyer_frames_pain2 [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain2 = {FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run};
+
+mframe_t flyer_frames_pain1 [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain1 = {FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run};
+
+mframe_t flyer_frames_defense [] = 
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,		// Hold this frame
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_defense = {FRAME_defens01, FRAME_defens06, flyer_frames_defense, NULL};
+
+mframe_t flyer_frames_bankright [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_bankright = {FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, NULL};
+
+mframe_t flyer_frames_bankleft [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, NULL};		
+
+
+void flyer_fire (edict_t *self, int flash_number)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+	
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 1, 1000, flash_number, effect);
+}
+
+void flyer_fireleft (edict_t *self)
+{
+	flyer_fire (self, MZ2_FLYER_BLASTER_1);
+}
+
+void flyer_fireright (edict_t *self)
+{
+	flyer_fire (self, MZ2_FLYER_BLASTER_2);
+}
+
+
+mframe_t flyer_frames_attack2 [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run};
+
+// PMM
+// circle strafe frames
+
+mframe_t flyer_frames_attack3 [] =
+{
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, flyer_fireleft,			// left gun
+		ai_charge, 10, flyer_fireright,		// right gun
+		ai_charge, 10, flyer_fireleft,			// left gun
+		ai_charge, 10, flyer_fireright,		// right gun
+		ai_charge, 10, flyer_fireleft,			// left gun
+		ai_charge, 10, flyer_fireright,		// right gun
+		ai_charge, 10, flyer_fireleft,			// left gun
+		ai_charge, 10, flyer_fireright,		// right gun
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL,
+		ai_charge, 10, NULL
+};
+mmove_t flyer_move_attack3 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run};
+// pmm
+
+void flyer_slash_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 0);
+	fire_hit (self, aim, 5, 0);
+	gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+void flyer_slash_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 0);
+	fire_hit (self, aim, 5, 0);
+	gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_start_melee [] =
+{
+		ai_charge, 0, flyer_pop_blades,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_start_melee = {FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee};
+
+mframe_t flyer_frames_end_melee [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_end_melee = {FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run};
+
+
+mframe_t flyer_frames_loop_melee [] =
+{
+		ai_charge, 0, NULL,		// Loop Start
+		ai_charge, 0, NULL,
+		ai_charge, 0, flyer_slash_left,		// Left Wing Strike
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, flyer_slash_right,	// Right Wing Strike
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL		// Loop Ends
+		
+};
+mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee};
+
+void flyer_loop_melee (edict_t *self)
+{
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	self->monsterinfo.currentmove = &flyer_move_loop_melee;
+}
+
+
+
+void flyer_attack (edict_t *self)
+{
+	float chance;
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	// 0% chance of circle in easy
+	// 50% chance in normal
+	// 75% chance in hard
+	// 86.67% chance in nightmare
+
+	if (self->mass > 50)
+	{
+		flyer_run (self);
+		return;
+	}
+
+	if (!skill->value)
+		chance = 0;
+	else
+		chance = 1.0 - (0.5/(float)(skill->value));
+
+	if (qrandom() > chance)
+	{
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+		self->monsterinfo.currentmove = &flyer_move_attack2;
+	}
+	else // circle strafe
+	{
+		if (qrandom () <= 0.5) // switch directions
+			self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		self->monsterinfo.currentmove = &flyer_move_attack3;
+	}
+}
+
+void flyer_setstart (edict_t *self)
+{
+	nextmove = ACTION_run;
+	self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+void flyer_nextmove (edict_t *self)
+{
+	if (nextmove == ACTION_attack1)
+		self->monsterinfo.currentmove = &flyer_move_start_melee;
+	else if (nextmove == ACTION_attack2)
+		self->monsterinfo.currentmove = &flyer_move_attack2;
+	else if (nextmove == ACTION_run)
+		self->monsterinfo.currentmove = &flyer_move_run;
+}
+
+void flyer_melee (edict_t *self)
+{
+//	flyer.nextmove = ACTION_attack1;
+//	self->monsterinfo.currentmove = &flyer_move_stop;
+	if (self->mass > 50)
+		flyer_run (self);
+	else
+		self->monsterinfo.currentmove = &flyer_move_start_melee;
+}
+
+void flyer_check_melee(edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+		if (qrandom() <= 0.8)
+			self->monsterinfo.currentmove = &flyer_move_loop_melee;
+		else
+			self->monsterinfo.currentmove = &flyer_move_end_melee;
+	else
+		self->monsterinfo.currentmove = &flyer_move_end_melee;
+}
+
+void flyer_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	//	pmm	 - kamikaze's don't feel pain
+	if (self->mass != 50)
+		return;
+	// pmm
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = rand() % 3;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain1;
+	}
+	else if (n == 1)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain3;
+	}
+}
+
+
+void flyer_die(edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	BecomeExplosion1(self);
+}
+
+// PMM - kamikaze code .. blow up if blocked	
+int flyer_blocked (edict_t *self, float)
+{
+	vec3_t origin;
+
+	// kamikaze = 100, normal = 50
+	if (self->mass == 100)
+	{
+		flyer_kamikaze_check(self);
+
+		// if the above didn't blow us up (i.e. I got blocked by the player)
+		if (self->inuse)
+		{
+			if (self->monsterinfo.commander && self->monsterinfo.commander->inuse && 
+				!strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
+			{
+				self->monsterinfo.commander->monsterinfo.monster_slots++;
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("suicide blocked, exploding.  %d slots left\n", self->monsterinfo.commander->monsterinfo.monster_slots);
+			}
+
+			VectorMA (self->s.origin, -0.02, self->velocity, origin);
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_ROCKET_EXPLOSION);
+			gi.WritePosition (origin);
+			gi.multicast (self->s.origin, MULTICAST_PHS);
+
+			G_FreeEdict (self);
+		}
+		return true;
+	}
+	// we're a normal flyer
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	return false;
+}
+
+/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_flyer (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// fix a map bug in jail5.bsp
+	if (!cistrcmp(level.mapname, "jail5") && (self->s.origin[2] == -104))
+	{
+		self->targetname = self->target;
+		self->target = NULL;
+	}
+
+	sound_sight = gi.soundindex ("flyer/flysght1.wav");
+	sound_idle = gi.soundindex ("flyer/flysrch1.wav");
+	sound_pain1 = gi.soundindex ("flyer/flypain1.wav");
+	sound_pain2 = gi.soundindex ("flyer/flypain2.wav");
+	sound_slash = gi.soundindex ("flyer/flyatck2.wav");
+	sound_sproing = gi.soundindex ("flyer/flyatck1.wav");
+	sound_die = gi.soundindex ("flyer/flydeth1.wav");
+
+	gi.soundindex ("flyer/flyatck3.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	// PMM - shortened to 16 from 32
+	VectorSet (self->maxs, 16, 16, 16);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->s.sound = gi.soundindex ("flyer/flyidle1.wav");
+
+	self->health = 50;
+	self->mass = 50;
+
+	self->pain = flyer_pain;
+	self->die = flyer_die;
+
+	self->monsterinfo.stand = flyer_stand;
+	self->monsterinfo.walk = flyer_walk;
+	self->monsterinfo.run = flyer_run;
+	self->monsterinfo.attack = flyer_attack;
+	self->monsterinfo.melee = flyer_melee;
+	self->monsterinfo.sight = flyer_sight;
+	self->monsterinfo.idle = flyer_idle;
+	self->monsterinfo.blocked = flyer_blocked;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &flyer_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
+
+// PMM - suicide fliers
+void SP_monster_kamikaze (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_sight = gi.soundindex ("flyer/flysght1.wav");
+	sound_idle = gi.soundindex ("flyer/flysrch1.wav");
+	sound_pain1 = gi.soundindex ("flyer/flypain1.wav");
+	sound_pain2 = gi.soundindex ("flyer/flypain2.wav");
+	sound_slash = gi.soundindex ("flyer/flyatck2.wav");
+	sound_sproing = gi.soundindex ("flyer/flyatck1.wav");
+	sound_die = gi.soundindex ("flyer/flydeth1.wav");
+
+	gi.soundindex ("flyer/flyatck3.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	// used to be 32 tall .. was WAY too big
+	VectorSet (self->maxs, 16, 16, 16);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->s.sound = gi.soundindex ("flyer/flyidle1.wav");
+	
+	self->s.effects |= EF_ROCKET;
+
+	self->health = 50;
+	// PMM - normal flyer has mass of 50
+	self->mass = 100;
+
+	self->pain = flyer_pain;
+	self->die = flyer_die;
+
+	self->monsterinfo.stand = flyer_stand;
+	self->monsterinfo.walk = flyer_walk;
+	self->monsterinfo.run = flyer_run;
+	self->monsterinfo.attack = flyer_attack;
+	self->monsterinfo.melee = flyer_melee;
+	self->monsterinfo.sight = flyer_sight;
+	self->monsterinfo.idle = flyer_idle;
+
+	self->monsterinfo.blocked = flyer_blocked;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &flyer_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_flyer.h
@@ -1,0 +1,163 @@
+// G:\quake2\baseq2\models/monsters/flyer
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define ACTION_nothing			0
+#define ACTION_attack1			1
+#define ACTION_attack2			2
+#define ACTION_run				3
+#define ACTION_walk				4
+
+#define FRAME_start01         	0
+#define FRAME_start02         	1
+#define FRAME_start03         	2
+#define FRAME_start04         	3
+#define FRAME_start05         	4
+#define FRAME_start06         	5
+#define FRAME_stop01          	6
+#define FRAME_stop02          	7
+#define FRAME_stop03          	8
+#define FRAME_stop04          	9
+#define FRAME_stop05          	10
+#define FRAME_stop06          	11
+#define FRAME_stop07          	12
+#define FRAME_stand01         	13
+#define FRAME_stand02         	14
+#define FRAME_stand03         	15
+#define FRAME_stand04         	16
+#define FRAME_stand05         	17
+#define FRAME_stand06         	18
+#define FRAME_stand07         	19
+#define FRAME_stand08         	20
+#define FRAME_stand09         	21
+#define FRAME_stand10         	22
+#define FRAME_stand11         	23
+#define FRAME_stand12         	24
+#define FRAME_stand13         	25
+#define FRAME_stand14         	26
+#define FRAME_stand15         	27
+#define FRAME_stand16         	28
+#define FRAME_stand17         	29
+#define FRAME_stand18         	30
+#define FRAME_stand19         	31
+#define FRAME_stand20         	32
+#define FRAME_stand21         	33
+#define FRAME_stand22         	34
+#define FRAME_stand23         	35
+#define FRAME_stand24         	36
+#define FRAME_stand25         	37
+#define FRAME_stand26         	38
+#define FRAME_stand27         	39
+#define FRAME_stand28         	40
+#define FRAME_stand29         	41
+#define FRAME_stand30         	42
+#define FRAME_stand31         	43
+#define FRAME_stand32         	44
+#define FRAME_stand33         	45
+#define FRAME_stand34         	46
+#define FRAME_stand35         	47
+#define FRAME_stand36         	48
+#define FRAME_stand37         	49
+#define FRAME_stand38         	50
+#define FRAME_stand39         	51
+#define FRAME_stand40         	52
+#define FRAME_stand41         	53
+#define FRAME_stand42         	54
+#define FRAME_stand43         	55
+#define FRAME_stand44         	56
+#define FRAME_stand45         	57
+#define FRAME_attak101        	58
+#define FRAME_attak102        	59
+#define FRAME_attak103        	60
+#define FRAME_attak104        	61
+#define FRAME_attak105        	62
+#define FRAME_attak106        	63
+#define FRAME_attak107        	64
+#define FRAME_attak108        	65
+#define FRAME_attak109        	66
+#define FRAME_attak110        	67
+#define FRAME_attak111        	68
+#define FRAME_attak112        	69
+#define FRAME_attak113        	70
+#define FRAME_attak114        	71
+#define FRAME_attak115        	72
+#define FRAME_attak116        	73
+#define FRAME_attak117        	74
+#define FRAME_attak118        	75
+#define FRAME_attak119        	76
+#define FRAME_attak120        	77
+#define FRAME_attak121        	78
+#define FRAME_attak201        	79
+#define FRAME_attak202        	80
+#define FRAME_attak203        	81
+#define FRAME_attak204        	82
+#define FRAME_attak205        	83
+#define FRAME_attak206        	84
+#define FRAME_attak207        	85
+#define FRAME_attak208        	86
+#define FRAME_attak209        	87
+#define FRAME_attak210        	88
+#define FRAME_attak211        	89
+#define FRAME_attak212        	90
+#define FRAME_attak213        	91
+#define FRAME_attak214        	92
+#define FRAME_attak215        	93
+#define FRAME_attak216        	94
+#define FRAME_attak217        	95
+#define FRAME_bankl01         	96
+#define FRAME_bankl02         	97
+#define FRAME_bankl03         	98
+#define FRAME_bankl04         	99
+#define FRAME_bankl05         	100
+#define FRAME_bankl06         	101
+#define FRAME_bankl07         	102
+#define FRAME_bankr01         	103
+#define FRAME_bankr02         	104
+#define FRAME_bankr03         	105
+#define FRAME_bankr04         	106
+#define FRAME_bankr05         	107
+#define FRAME_bankr06         	108
+#define FRAME_bankr07         	109
+#define FRAME_rollf01         	110
+#define FRAME_rollf02         	111
+#define FRAME_rollf03         	112
+#define FRAME_rollf04         	113
+#define FRAME_rollf05         	114
+#define FRAME_rollf06         	115
+#define FRAME_rollf07         	116
+#define FRAME_rollf08         	117
+#define FRAME_rollf09         	118
+#define FRAME_rollr01         	119
+#define FRAME_rollr02         	120
+#define FRAME_rollr03         	121
+#define FRAME_rollr04         	122
+#define FRAME_rollr05         	123
+#define FRAME_rollr06         	124
+#define FRAME_rollr07         	125
+#define FRAME_rollr08         	126
+#define FRAME_rollr09         	127
+#define FRAME_defens01        	128
+#define FRAME_defens02        	129
+#define FRAME_defens03        	130
+#define FRAME_defens04        	131
+#define FRAME_defens05        	132
+#define FRAME_defens06        	133
+#define FRAME_pain101         	134
+#define FRAME_pain102         	135
+#define FRAME_pain103         	136
+#define FRAME_pain104         	137
+#define FRAME_pain105         	138
+#define FRAME_pain106         	139
+#define FRAME_pain107         	140
+#define FRAME_pain108         	141
+#define FRAME_pain109         	142
+#define FRAME_pain201         	143
+#define FRAME_pain202         	144
+#define FRAME_pain203         	145
+#define FRAME_pain204         	146
+#define FRAME_pain301         	147
+#define FRAME_pain302         	148
+#define FRAME_pain303         	149
+#define FRAME_pain304         	150
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_gladiator.c
@@ -1,0 +1,379 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gladiator.h"
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_gun;
+static int	sound_cleaver_swing;
+static int	sound_cleaver_hit;
+static int	sound_cleaver_miss;
+static int	sound_idle;
+static int	sound_search;
+static int	sound_sight;
+
+
+void gladiator_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void gladiator_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gladiator_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void gladiator_cleaver_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t gladiator_move_stand = {FRAME_stand1, FRAME_stand7, gladiator_frames_stand, NULL};
+
+void gladiator_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_stand;
+}
+
+
+mframe_t gladiator_frames_walk [] =
+{
+	ai_walk, 15, NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 12, NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 8,  NULL
+};
+mmove_t gladiator_move_walk = {FRAME_walk1, FRAME_walk16, gladiator_frames_walk, NULL};
+
+void gladiator_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_walk;
+}
+
+
+mframe_t gladiator_frames_run [] =
+{
+	ai_run, 23,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 21,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 13,	NULL
+};
+mmove_t gladiator_move_run = {FRAME_run1, FRAME_run6, gladiator_frames_run, NULL};
+
+void gladiator_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &gladiator_move_stand;
+	else
+		self->monsterinfo.currentmove = &gladiator_move_run;
+}
+
+
+void GaldiatorMelee (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4);
+	if (fire_hit (self, aim, (20 + (rand() %5)), 300))
+		gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_attack_melee [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladiator_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GaldiatorMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladiator_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GaldiatorMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gladiator_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladiator_frames_attack_melee, gladiator_run};
+
+void gladiator_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_attack_melee;
+}
+
+
+void GladiatorGun (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start);
+
+	// calc direction to where we targted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1);
+}
+
+mframe_t gladiator_frames_attack_gun [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GladiatorGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gladiator_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run};
+
+void gladiator_attack(edict_t *self)
+{
+	float	range;
+	vec3_t	v;
+
+	// a small safe zone
+	VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+	range = VectorLength(v);
+	if (range <= (MELEE_DISTANCE + 32))
+		return;
+
+	// charge up the railgun
+	gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+	self->monsterinfo.currentmove = &gladiator_move_attack_gun;
+}
+
+
+mframe_t gladiator_frames_pain [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_pain = {FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_run};
+
+mframe_t gladiator_frames_pain_air [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_pain_air = {FRAME_painup1, FRAME_painup7, gladiator_frames_pain_air, gladiator_run};
+
+void gladiator_pain (edict_t *self, edict_t *, float, int)
+{
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladiator_move_pain))
+			self->monsterinfo.currentmove = &gladiator_move_pain_air;
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (self->velocity[2] > 100)
+		self->monsterinfo.currentmove = &gladiator_move_pain_air;
+	else
+		self->monsterinfo.currentmove = &gladiator_move_pain;
+	
+}
+
+
+void gladiator_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t gladiator_frames_death [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_death, gladiator_dead};
+
+void gladiator_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &gladiator_move_death;
+}
+
+//===========
+//PGM
+qboolean gladiator_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_gladiator (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+
+	sound_pain1 = gi.soundindex ("gladiator/pain.wav");	
+	sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav");	
+	sound_die = gi.soundindex ("gladiator/glddeth2.wav");	
+	sound_gun = gi.soundindex ("gladiator/railgun.wav");
+	sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav");
+	sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav");
+	sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav");
+	sound_idle = gi.soundindex ("gladiator/gldidle1.wav");
+	sound_search = gi.soundindex ("gladiator/gldsrch1.wav");
+	sound_sight = gi.soundindex ("gladiator/sight.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gladiatr/tris.md2");
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 64);
+
+	self->health = 400;
+	self->gib_health = -175;
+	self->mass = 400;
+
+	self->pain = gladiator_pain;
+	self->die = gladiator_die;
+
+	self->monsterinfo.stand = gladiator_stand;
+	self->monsterinfo.walk = gladiator_walk;
+	self->monsterinfo.run = gladiator_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = gladiator_attack;
+	self->monsterinfo.melee = gladiator_melee;
+	self->monsterinfo.sight = gladiator_sight;
+	self->monsterinfo.idle = gladiator_idle;
+	self->monsterinfo.search = gladiator_search;
+	self->monsterinfo.blocked = gladiator_blocked;		// PGM
+
+	gi.linkentity (self);
+	self->monsterinfo.currentmove = &gladiator_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_gladiator.h
@@ -1,0 +1,96 @@
+// G:\quake2\baseq2\models/monsters/gladiatr
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_stand6          	5
+#define FRAME_stand7          	6
+#define FRAME_walk1           	7
+#define FRAME_walk2           	8
+#define FRAME_walk3           	9
+#define FRAME_walk4           	10
+#define FRAME_walk5           	11
+#define FRAME_walk6           	12
+#define FRAME_walk7           	13
+#define FRAME_walk8           	14
+#define FRAME_walk9           	15
+#define FRAME_walk10          	16
+#define FRAME_walk11          	17
+#define FRAME_walk12          	18
+#define FRAME_walk13          	19
+#define FRAME_walk14          	20
+#define FRAME_walk15          	21
+#define FRAME_walk16          	22
+#define FRAME_run1            	23
+#define FRAME_run2            	24
+#define FRAME_run3            	25
+#define FRAME_run4            	26
+#define FRAME_run5            	27
+#define FRAME_run6            	28
+#define FRAME_melee1          	29
+#define FRAME_melee2          	30
+#define FRAME_melee3          	31
+#define FRAME_melee4          	32
+#define FRAME_melee5          	33
+#define FRAME_melee6          	34
+#define FRAME_melee7          	35
+#define FRAME_melee8          	36
+#define FRAME_melee9          	37
+#define FRAME_melee10         	38
+#define FRAME_melee11         	39
+#define FRAME_melee12         	40
+#define FRAME_melee13         	41
+#define FRAME_melee14         	42
+#define FRAME_melee15         	43
+#define FRAME_melee16         	44
+#define FRAME_melee17         	45
+#define FRAME_attack1         	46
+#define FRAME_attack2         	47
+#define FRAME_attack3         	48
+#define FRAME_attack4         	49
+#define FRAME_attack5         	50
+#define FRAME_attack6         	51
+#define FRAME_attack7         	52
+#define FRAME_attack8         	53
+#define FRAME_attack9         	54
+#define FRAME_pain1           	55
+#define FRAME_pain2           	56
+#define FRAME_pain3           	57
+#define FRAME_pain4           	58
+#define FRAME_pain5           	59
+#define FRAME_pain6           	60
+#define FRAME_death1          	61
+#define FRAME_death2          	62
+#define FRAME_death3          	63
+#define FRAME_death4          	64
+#define FRAME_death5          	65
+#define FRAME_death6          	66
+#define FRAME_death7          	67
+#define FRAME_death8          	68
+#define FRAME_death9          	69
+#define FRAME_death10         	70
+#define FRAME_death11         	71
+#define FRAME_death12         	72
+#define FRAME_death13         	73
+#define FRAME_death14         	74
+#define FRAME_death15         	75
+#define FRAME_death16         	76
+#define FRAME_death17         	77
+#define FRAME_death18         	78
+#define FRAME_death19         	79
+#define FRAME_death20         	80
+#define FRAME_death21         	81
+#define FRAME_death22         	82
+#define FRAME_painup1         	83
+#define FRAME_painup2         	84
+#define FRAME_painup3         	85
+#define FRAME_painup4         	86
+#define FRAME_painup5         	87
+#define FRAME_painup6         	88
+#define FRAME_painup7         	89
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_gunner.c
@@ -1,0 +1,1058 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gunner.h"
+
+
+static int	sound_pain;
+static int	sound_pain2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_open;
+static int	sound_search;
+static int	sound_sight;
+
+void gunner_idlesound (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void gunner_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gunner_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+qboolean visible (edict_t *self, edict_t *other);
+void GunnerGrenade (edict_t *self);
+void GunnerFire (edict_t *self);
+void gunner_fire_chain(edict_t *self);
+void gunner_refire_chain(edict_t *self);
+
+
+void gunner_stand (edict_t *self);
+
+mframe_t gunner_frames_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_idlesound,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	gunner_move_fidget = {FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand};
+
+void gunner_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() <= 0.05)
+		self->monsterinfo.currentmove = &gunner_move_fidget;
+}
+
+mframe_t gunner_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget
+};
+mmove_t	gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL};
+
+void gunner_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &gunner_move_stand;
+}
+
+
+mframe_t gunner_frames_walk [] =
+{
+	ai_walk, 0, NULL,
+	ai_walk, 3, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 2, NULL,
+	ai_walk, 6, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 2, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 4, NULL
+};
+mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL};
+
+void gunner_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_walk;
+}
+
+mframe_t gunner_frames_run [] =
+{
+	ai_run, 26, NULL,
+	ai_run, 9,  NULL,
+	ai_run, 9,  NULL,
+	ai_run, 9,  monster_done_dodge,
+	ai_run, 15, NULL,
+	ai_run, 10, NULL,
+	ai_run, 13, NULL,
+	ai_run, 6,  NULL
+};
+
+mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL};
+
+void gunner_run (edict_t *self)
+{
+	monster_done_dodge(self);
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &gunner_move_stand;
+	else
+		self->monsterinfo.currentmove = &gunner_move_run;
+}
+
+mframe_t gunner_frames_runandshoot [] =
+{
+	ai_run, 32, NULL,
+	ai_run, 15, NULL,
+	ai_run, 10, NULL,
+	ai_run, 18, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 20, NULL
+};
+
+mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL};
+
+void gunner_runandshoot (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_runandshoot;
+}
+
+mframe_t gunner_frames_pain3 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 1,	 NULL
+};
+mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run};
+
+mframe_t gunner_frames_pain2 [] =
+{
+	ai_move, -2, NULL,
+	ai_move, 11, NULL,
+	ai_move, 6,	 NULL,
+	ai_move, 2,	 NULL,
+	ai_move, -1, NULL,
+	ai_move, -7, NULL,
+	ai_move, -2, NULL,
+	ai_move, -7, NULL
+};
+mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run};
+
+mframe_t gunner_frames_pain1 [] =
+{
+	ai_move, 2,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -5, NULL,
+	ai_move, 3,	 NULL,
+	ai_move, -1, NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 2,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -2, NULL,
+	ai_move, -2, NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run};
+
+void gunner_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	monster_done_dodge (self);
+
+	if (!self->groundentity)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("gunner: pain avoided due to no ground\n");
+		return;
+	}
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (rand()&1)
+		gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 10)
+		self->monsterinfo.currentmove = &gunner_move_pain3;
+	else if (damage <= 25)
+		self->monsterinfo.currentmove = &gunner_move_pain2;
+	else
+		self->monsterinfo.currentmove = &gunner_move_pain1;
+
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+}
+
+void gunner_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t gunner_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -7, NULL,
+	ai_move, -3, NULL,
+	ai_move, -5, NULL,
+	ai_move, 8,	 NULL,
+	ai_move, 6,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead};
+
+void gunner_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &gunner_move_death;
+}
+
+// PMM - changed to duck code for new dodge
+
+//
+// this is specific to the gunner, leave it be
+//
+void gunner_duck_down (edict_t *self)
+{
+//	if (self->monsterinfo.aiflags & AI_DUCKED)
+//		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	if (skill->value >= 2)
+	{
+		if (qrandom() > 0.5)
+			GunnerGrenade (self);
+	}
+
+//	self->maxs[2] -= 32;
+	self->maxs[2] = self->monsterinfo.base_height - 32;
+	self->takedamage = DAMAGE_YES;
+	if (self->monsterinfo.duck_wait_time < level.time)
+		self->monsterinfo.duck_wait_time = level.time + 1;
+	gi.linkentity (self);
+}
+
+mframe_t gunner_frames_duck [] =
+{
+	ai_move, 1,  gunner_duck_down,
+	ai_move, 1,  NULL,
+	ai_move, 1,  monster_duck_hold,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -1, NULL,
+	ai_move, 0,  monster_duck_up,
+	ai_move, -1, NULL
+};
+mmove_t	gunner_move_duck = {FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run};
+
+// PMM - gunner dodge moved below so I know about attack sequences
+
+void gunner_opengun (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
+}
+
+void GunnerFire (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	target;
+	vec3_t	aim;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	// project enemy back a bit and target there
+	VectorCopy (self->enemy->s.origin, target);
+	VectorMA (target, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+
+	VectorSubtract (target, start, aim);
+	VectorNormalize (aim);
+	monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+qboolean gunner_grenade_check(edict_t *self)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	trace_t		tr;
+	vec3_t		target, dir;
+
+	if(!self->enemy)
+		return false;
+
+	// if the player is above my head, use machinegun.
+
+	// check for flag telling us that we're blindfiring
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		if (self->s.origin[2]+self->viewheight < self->monsterinfo.blind_fire_target[2])
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf("blind_fire_target is above my head, using machinegun\n");
+			return false;
+		}
+	}
+	else if(self->absmax[2] <= self->enemy->absmin[2])
+	{
+//		if(g_showlogic && g_showlogic->value)
+//			gi.dprintf("player is above my head, using machinegun\n");
+		return false;
+	}
+
+	// check to see that we can trace to the player before we start
+	// tossing grenades around.
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GUNNER_GRENADE_1], forward, right, start);
+
+	// pmm - check for blindfire flag
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		VectorCopy (self->monsterinfo.blind_fire_target, target);
+	else
+		VectorCopy (self->enemy->s.origin, target);
+
+	// see if we're too close
+	VectorSubtract (self->s.origin, target, dir);
+
+	if (VectorLength(dir) < 100)
+		return false;
+
+	tr = gi.trace(start, vec3_origin, vec3_origin, target, self, MASK_SHOT);
+	if(tr.ent == self->enemy || tr.fraction == 1)
+		return true;
+
+//	if(g_showlogic && g_showlogic->value)
+//		gi.dprintf("can't trace to target, using machinegun\n");
+	return false;
+}
+
+void GunnerGrenade (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	aim;
+	int		flash_number;
+	float	spread;
+	float	pitch = 0.0;
+	// PMM
+	vec3_t	target;	
+	qboolean blindfire = false;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	// pmm
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		blindfire = true;
+
+	if (self->s.frame == FRAME_attak105)
+	{
+		spread = .02;
+		flash_number = MZ2_GUNNER_GRENADE_1;
+	}
+	else if (self->s.frame == FRAME_attak108)
+	{
+		spread = .05;
+		flash_number = MZ2_GUNNER_GRENADE_2;
+	}
+	else if (self->s.frame == FRAME_attak111)
+	{
+		spread = .08;
+		flash_number = MZ2_GUNNER_GRENADE_3;
+	}
+	else // (self->s.frame == FRAME_attak114)
+	{
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		spread = .11;
+		flash_number = MZ2_GUNNER_GRENADE_4;
+	}
+
+	//	pmm
+	// if we're shooting blind and we still can't see our enemy
+	if ((blindfire) && (!visible(self, self->enemy)))
+	{
+		// and we have a valid blind_fire_target
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+		
+//		gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target));
+//		gi.dprintf ("GunnerGrenade: ideal yaw is %f\n", self->ideal_yaw);
+		VectorCopy (self->monsterinfo.blind_fire_target, target);
+	}
+	else
+		VectorCopy (self->s.origin, target);
+	// pmm
+
+	AngleVectors (self->s.angles, forward, right, up);	//PGM
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+//PGM
+	if(self->enemy)
+	{
+		float	dist;
+
+//		VectorSubtract(self->enemy->s.origin, self->s.origin, aim);
+		VectorSubtract(target, self->s.origin, aim);
+		dist = VectorLength(aim);
+
+		// aim up if they're on the same level as me and far away.
+		if((dist > 512) && (aim[2] < 64) && (aim[2] > -64))
+		{
+			aim[2] += (dist - 512);
+		}
+
+		VectorNormalize (aim);
+		pitch = aim[2];
+		if(pitch > 0.4)
+		{
+			pitch = 0.4;
+		}
+		else if(pitch < -0.5)
+			pitch = -0.5;
+	}
+//PGM
+
+	//FIXME : do a spread -225 -75 75 225 degrees around forward
+//	VectorCopy (forward, aim);
+	VectorMA (forward, spread, right, aim);
+	VectorMA (aim, pitch, up, aim);
+
+	monster_fire_grenade (self, start, aim, 50, 600, flash_number);
+}
+
+mframe_t gunner_frames_attack_chain [] =
+{
+	/*
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	*/
+	ai_charge, 0, gunner_opengun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_attack_chain = {FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain};
+
+mframe_t gunner_frames_fire_chain [] =
+{
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire
+};
+mmove_t gunner_move_fire_chain = {FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain};
+
+mframe_t gunner_frames_endfire_chain [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_endfire_chain = {FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run};
+
+void gunner_blind_check (edict_t *self)
+{
+	vec3_t	aim;
+
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, aim);
+		self->ideal_yaw = vectoyaw(aim);
+		
+//		gi.dprintf ("blind_fire_target = %s\n", vtos (self->monsterinfo.blind_fire_target));
+//		gi.dprintf ("gunner_attack: ideal yaw is %f\n", self->ideal_yaw);
+	}
+}
+
+mframe_t gunner_frames_attack_grenade [] =
+{
+	ai_charge, 0, gunner_blind_check,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run};
+
+void gunner_attack(edict_t *self)
+{
+	float chance, r;
+
+	monster_done_dodge(self);
+
+	// PMM 
+	if (self->monsterinfo.attack_state == AS_BLIND)
+	{
+		// setup shot probabilities
+		if (self->monsterinfo.blind_fire_delay < 1.0)
+			chance = 1.0;
+		else if (self->monsterinfo.blind_fire_delay < 7.5)
+			chance = 0.4;
+		else
+			chance = 0.1;
+
+		r = qrandom();
+
+		// minimum of 2 seconds, plus 0-3, after the shots are done
+		self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + qrandom()*3.0;
+
+		// don't shoot at the origin
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+
+		// don't shoot if the dice say not to
+		if (r > chance)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("blindfire - NO SHOT\n");
+			return;
+		}
+
+		// turn on manual steering to signal both manual steering and blindfire
+		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+		if (gunner_grenade_check(self))
+		{
+			// if the check passes, go for the attack
+			self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+			self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		}
+		// pmm - should this be active?
+//		else
+//			self->monsterinfo.currentmove = &gunner_move_attack_chain;
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("blind grenade check failed, doing nothing\n");
+
+		// turn off blindfire flag
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		return;
+	}
+	// pmm
+
+	// PGM - gunner needs to use his chaingun if he's being attacked by a tesla.
+	if ((range (self, self->enemy) == RANGE_MELEE) || self->bad_area)
+	{
+		self->monsterinfo.currentmove = &gunner_move_attack_chain;
+	}
+	else
+	{
+		if (qrandom() <= 0.5 && gunner_grenade_check(self))
+			self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+		else
+			self->monsterinfo.currentmove = &gunner_move_attack_chain;
+	}
+}
+
+void gunner_fire_chain(edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_fire_chain;
+}
+
+void gunner_refire_chain(edict_t *self)
+{
+	if (self->enemy->health > 0)
+		if ( visible (self, self->enemy) )
+			if (qrandom() <= 0.5)
+			{
+				self->monsterinfo.currentmove = &gunner_move_fire_chain;
+				return;
+			}
+	self->monsterinfo.currentmove = &gunner_move_endfire_chain;
+}
+/*
+void gunner_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+// original quake2 dodge code
+
+	if (random() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &gunner_move_duck;
+
+//===========
+//PMM - rogue rewrite of gunner dodge code.
+	float	r;
+	float	height;
+	int		shooting = 0;
+
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	r = qrandom();
+	if (r > (0.25*((skill->value)+1)))
+		return;
+
+	if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
+		)
+	{
+		shooting = 1;
+	}
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		height = self->absmax[2];
+	}
+	else
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+	}
+
+	// check to see if it makes sense to duck
+	if (tr->endpos[2] <= height)
+	{
+		vec3_t right, diff;
+		if (shooting)
+		{
+			self->monsterinfo.attack_state = AS_SLIDING;
+			return;
+		}
+		AngleVectors (self->s.angles, NULL, right, NULL);
+		VectorSubtract (tr->endpos, self->s.origin, diff);
+		if (DotProduct (right, diff) < 0)
+		{
+			self->monsterinfo.lefty = 1;
+		}
+		// if it doesn't sense to duck, try to strafe away
+		monster_done_dodge (self);
+		self->monsterinfo.currentmove = &gunner_move_run;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		return;
+	}
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &gunner_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.aiflags |= AI_DODGING;
+		return;
+	}
+
+	if (!shooting)
+	{
+		self->monsterinfo.currentmove = &gunner_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+		self->monsterinfo.aiflags |= AI_DODGING;
+	}
+	return;
+//PMM
+//===========
+}
+*/
+//===========
+//PGM
+void gunner_jump_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void gunner_jump2_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 150, forward, self->velocity);
+	VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void gunner_jump_wait_land (edict_t *self)
+{
+	if(self->groundentity == NULL)
+	{
+		self->monsterinfo.nextframe = self->s.frame;
+
+		if(monster_jump_finished (self))
+			self->monsterinfo.nextframe = self->s.frame + 1;
+	}
+	else 
+		self->monsterinfo.nextframe = self->s.frame + 1;
+}
+
+mframe_t gunner_frames_jump [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, gunner_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, gunner_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gunner_move_jump = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run };
+
+mframe_t gunner_frames_jump2 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -4, NULL,
+	ai_move, -4, NULL,
+	ai_move, 0, gunner_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, gunner_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gunner_move_jump2 = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run };
+
+void gunner_jump (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	monster_done_dodge (self);
+
+	if(self->enemy->s.origin[2] > self->s.origin[2])
+		self->monsterinfo.currentmove = &gunner_move_jump2;
+	else
+		self->monsterinfo.currentmove = &gunner_move_jump;
+}
+
+//===========
+//PGM
+qboolean gunner_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	if(blocked_checkjump (self, dist, 192, 40))
+	{
+		gunner_jump(self);
+		return true;
+	}
+
+	return false;
+}
+//PGM
+//===========
+
+// PMM - new duck code
+void gunner_duck (edict_t *self, float eta)
+{
+	if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
+		(self->monsterinfo.currentmove == &gunner_move_jump))
+	{
+		return;
+	}
+
+	if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
+		)
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DUCKED;
+			return;
+		}
+	}
+
+	if (skill->value == 0)
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	else
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+
+	// has to be done immediately otherwise he can get stuck
+	gunner_duck_down(self);
+
+	self->monsterinfo.nextframe = FRAME_duck01;
+	self->monsterinfo.currentmove = &gunner_move_duck;
+	return;
+}
+
+void gunner_sidestep (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &gunner_move_jump2) ||
+		(self->monsterinfo.currentmove == &gunner_move_jump))
+	{
+		return;
+	}
+
+	if ((self->monsterinfo.currentmove == &gunner_move_attack_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_fire_chain) ||
+		(self->monsterinfo.currentmove == &gunner_move_attack_grenade)
+		)
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DODGING;
+			return;
+		}
+	}
+
+	if (self->monsterinfo.currentmove != &gunner_move_run)
+		self->monsterinfo.currentmove = &gunner_move_run;
+}
+
+
+/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_gunner (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_death = gi.soundindex ("gunner/death1.wav");	
+	sound_pain = gi.soundindex ("gunner/gunpain2.wav");	
+	sound_pain2 = gi.soundindex ("gunner/gunpain1.wav");	
+	sound_idle = gi.soundindex ("gunner/gunidle1.wav");	
+	sound_open = gi.soundindex ("gunner/gunatck1.wav");	
+	sound_search = gi.soundindex ("gunner/gunsrch1.wav");	
+	sound_sight = gi.soundindex ("gunner/sight1.wav");	
+
+	gi.soundindex ("gunner/gunatck2.wav");
+	gi.soundindex ("gunner/gunatck3.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 175;
+	self->gib_health = -70;
+	self->mass = 200;
+
+	self->pain = gunner_pain;
+	self->die = gunner_die;
+
+	self->monsterinfo.stand = gunner_stand;
+	self->monsterinfo.walk = gunner_walk;
+	self->monsterinfo.run = gunner_run;
+	// pmm
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.duck = gunner_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+	self->monsterinfo.sidestep = gunner_sidestep;
+//	self->monsterinfo.dodge = gunner_dodge;
+	// pmm
+	self->monsterinfo.attack = gunner_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = gunner_sight;
+	self->monsterinfo.search = gunner_search;
+	self->monsterinfo.blocked = gunner_blocked;		//PGM
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &gunner_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	// PMM
+	self->monsterinfo.blindfire = true;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_gunner.h
@@ -1,0 +1,225 @@
+// G:\quake2\baseq2\models/gunner
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_stand41         	40
+#define FRAME_stand42         	41
+#define FRAME_stand43         	42
+#define FRAME_stand44         	43
+#define FRAME_stand45         	44
+#define FRAME_stand46         	45
+#define FRAME_stand47         	46
+#define FRAME_stand48         	47
+#define FRAME_stand49         	48
+#define FRAME_stand50         	49
+#define FRAME_stand51         	50
+#define FRAME_stand52         	51
+#define FRAME_stand53         	52
+#define FRAME_stand54         	53
+#define FRAME_stand55         	54
+#define FRAME_stand56         	55
+#define FRAME_stand57         	56
+#define FRAME_stand58         	57
+#define FRAME_stand59         	58
+#define FRAME_stand60         	59
+#define FRAME_stand61         	60
+#define FRAME_stand62         	61
+#define FRAME_stand63         	62
+#define FRAME_stand64         	63
+#define FRAME_stand65         	64
+#define FRAME_stand66         	65
+#define FRAME_stand67         	66
+#define FRAME_stand68         	67
+#define FRAME_stand69         	68
+#define FRAME_stand70         	69
+#define FRAME_walk01          	70
+#define FRAME_walk02          	71
+#define FRAME_walk03          	72
+#define FRAME_walk04          	73
+#define FRAME_walk05          	74
+#define FRAME_walk06          	75
+#define FRAME_walk07          	76
+#define FRAME_walk08          	77
+#define FRAME_walk09          	78
+#define FRAME_walk10          	79
+#define FRAME_walk11          	80
+#define FRAME_walk12          	81
+#define FRAME_walk13          	82
+#define FRAME_walk14          	83
+#define FRAME_walk15          	84
+#define FRAME_walk16          	85
+#define FRAME_walk17          	86
+#define FRAME_walk18          	87
+#define FRAME_walk19          	88
+#define FRAME_walk20          	89
+#define FRAME_walk21          	90
+#define FRAME_walk22          	91
+#define FRAME_walk23          	92
+#define FRAME_walk24          	93
+#define FRAME_run01           	94
+#define FRAME_run02           	95
+#define FRAME_run03           	96
+#define FRAME_run04           	97
+#define FRAME_run05           	98
+#define FRAME_run06           	99
+#define FRAME_run07           	100
+#define FRAME_run08           	101
+#define FRAME_runs01          	102
+#define FRAME_runs02          	103
+#define FRAME_runs03          	104
+#define FRAME_runs04          	105
+#define FRAME_runs05          	106
+#define FRAME_runs06          	107
+#define FRAME_attak101        	108
+#define FRAME_attak102        	109
+#define FRAME_attak103        	110
+#define FRAME_attak104        	111
+#define FRAME_attak105        	112
+#define FRAME_attak106        	113
+#define FRAME_attak107        	114
+#define FRAME_attak108        	115
+#define FRAME_attak109        	116
+#define FRAME_attak110        	117
+#define FRAME_attak111        	118
+#define FRAME_attak112        	119
+#define FRAME_attak113        	120
+#define FRAME_attak114        	121
+#define FRAME_attak115        	122
+#define FRAME_attak116        	123
+#define FRAME_attak117        	124
+#define FRAME_attak118        	125
+#define FRAME_attak119        	126
+#define FRAME_attak120        	127
+#define FRAME_attak121        	128
+#define FRAME_attak201        	129
+#define FRAME_attak202        	130
+#define FRAME_attak203        	131
+#define FRAME_attak204        	132
+#define FRAME_attak205        	133
+#define FRAME_attak206        	134
+#define FRAME_attak207        	135
+#define FRAME_attak208        	136
+#define FRAME_attak209        	137
+#define FRAME_attak210        	138
+#define FRAME_attak211        	139
+#define FRAME_attak212        	140
+#define FRAME_attak213        	141
+#define FRAME_attak214        	142
+#define FRAME_attak215        	143
+#define FRAME_attak216        	144
+#define FRAME_attak217        	145
+#define FRAME_attak218        	146
+#define FRAME_attak219        	147
+#define FRAME_attak220        	148
+#define FRAME_attak221        	149
+#define FRAME_attak222        	150
+#define FRAME_attak223        	151
+#define FRAME_attak224        	152
+#define FRAME_attak225        	153
+#define FRAME_attak226        	154
+#define FRAME_attak227        	155
+#define FRAME_attak228        	156
+#define FRAME_attak229        	157
+#define FRAME_attak230        	158
+#define FRAME_pain101         	159
+#define FRAME_pain102         	160
+#define FRAME_pain103         	161
+#define FRAME_pain104         	162
+#define FRAME_pain105         	163
+#define FRAME_pain106         	164
+#define FRAME_pain107         	165
+#define FRAME_pain108         	166
+#define FRAME_pain109         	167
+#define FRAME_pain110         	168
+#define FRAME_pain111         	169
+#define FRAME_pain112         	170
+#define FRAME_pain113         	171
+#define FRAME_pain114         	172
+#define FRAME_pain115         	173
+#define FRAME_pain116         	174
+#define FRAME_pain117         	175
+#define FRAME_pain118         	176
+#define FRAME_pain201         	177
+#define FRAME_pain202         	178
+#define FRAME_pain203         	179
+#define FRAME_pain204         	180
+#define FRAME_pain205         	181
+#define FRAME_pain206         	182
+#define FRAME_pain207         	183
+#define FRAME_pain208         	184
+#define FRAME_pain301         	185
+#define FRAME_pain302         	186
+#define FRAME_pain303         	187
+#define FRAME_pain304         	188
+#define FRAME_pain305         	189
+#define FRAME_death01         	190
+#define FRAME_death02         	191
+#define FRAME_death03         	192
+#define FRAME_death04         	193
+#define FRAME_death05         	194
+#define FRAME_death06         	195
+#define FRAME_death07         	196
+#define FRAME_death08         	197
+#define FRAME_death09         	198
+#define FRAME_death10         	199
+#define FRAME_death11         	200
+#define FRAME_duck01          	201
+#define FRAME_duck02          	202
+#define FRAME_duck03          	203
+#define FRAME_duck04          	204
+#define FRAME_duck05          	205
+#define FRAME_duck06          	206
+#define FRAME_duck07          	207
+#define FRAME_duck08          	208
+#define FRAME_jump01			209
+#define FRAME_jump02			210
+#define FRAME_jump03			211
+#define FRAME_jump04			212
+#define FRAME_jump05			213
+#define FRAME_jump06			214
+#define FRAME_jump07			215
+#define FRAME_jump08			216
+#define FRAME_jump09			217
+#define FRAME_jump10			218
+
+#define MODEL_SCALE		1.150000
--- /dev/null
+++ b/rogue/m_hover.c
@@ -1,0 +1,786 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_hover.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_death1;
+static int	sound_death2;
+static int	sound_sight;
+static int	sound_search1;
+static int	sound_search2;
+
+// daedalus sounds
+static int	daed_sound_pain1;
+static int	daed_sound_pain2;
+static int	daed_sound_death1;
+static int	daed_sound_death2;
+static int	daed_sound_sight;
+static int	daed_sound_search1;
+static int	daed_sound_search2;
+
+
+void hover_sight (edict_t *self, edict_t *)
+{
+	// PMM - daedalus sounds
+	if (self->mass < 225)
+		gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0);
+}
+
+void hover_search (edict_t *self)
+{
+	// PMM - daedalus sounds
+	if (self->mass < 225)
+	{
+		if (qrandom() < 0.5)
+			gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		if (qrandom() < 0.5)
+			gi.sound (self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0);
+	}
+}
+
+
+void hover_run (edict_t *self);
+void hover_stand (edict_t *self);
+void hover_dead (edict_t *self);
+void hover_attack (edict_t *self);
+void hover_reattack (edict_t *self);
+void hover_fire_blaster (edict_t *self);
+void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+mframe_t hover_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	hover_move_stand = {FRAME_stand01, FRAME_stand30, hover_frames_stand, NULL};
+/*
+mframe_t hover_frames_stop1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_stop1 = {FRAME_stop101, FRAME_stop109, hover_frames_stop1, NULL};
+
+mframe_t hover_frames_stop2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_stop2 = {FRAME_stop201, FRAME_stop208, hover_frames_stop2, NULL};
+*/
+/*
+mframe_t hover_frames_takeoff [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-9,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_takeoff = {FRAME_takeof01, FRAME_takeof30, hover_frames_takeoff, NULL};
+*/
+mframe_t hover_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_pain3 = {FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run};
+
+mframe_t hover_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_pain2 = {FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run};
+
+mframe_t hover_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	-8,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	4,	NULL
+};
+mmove_t hover_move_pain1 = {FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run};
+
+/*
+mframe_t hover_frames_land [] =
+{
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_land = {FRAME_land01, FRAME_land01, hover_frames_land, NULL};
+*/
+/*
+mframe_t hover_frames_forward [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_forward = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_forward, NULL};
+*/
+mframe_t hover_frames_walk [] =
+{
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL
+};
+mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NULL};
+
+mframe_t hover_frames_run [] =
+{
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL
+};
+mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL};
+
+mframe_t hover_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-10,NULL,
+	ai_move,	3,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	4,	NULL,
+	ai_move,	7,	NULL
+};
+mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead};
+/*
+mframe_t hover_frames_backward [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_backward = {FRAME_backwd01, FRAME_backwd24, hover_frames_backward, NULL};
+*/
+mframe_t hover_frames_start_attack [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack};
+
+mframe_t hover_frames_attack1 [] =
+{
+	ai_charge,	-10,	hover_fire_blaster,
+	ai_charge,	-10,	hover_fire_blaster,
+	ai_charge,	0,		hover_reattack,
+};
+mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, NULL};
+
+
+mframe_t hover_frames_end_attack [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run};
+
+/* PMM - circle strafing code */
+
+mframe_t hover_frames_start_attack2 [] =
+{
+	ai_charge,	15,	NULL,
+	ai_charge,	15,	NULL,
+	ai_charge,	15,	NULL
+};
+mmove_t hover_move_start_attack2 = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack2, hover_attack};
+
+mframe_t hover_frames_attack2 [] =
+{
+	ai_charge,	10,	hover_fire_blaster,
+	ai_charge,	10,	hover_fire_blaster,
+	ai_charge,	10,		hover_reattack,
+};
+mmove_t hover_move_attack2 = {FRAME_attak104, FRAME_attak106, hover_frames_attack2, NULL};
+
+
+mframe_t hover_frames_end_attack2 [] =
+{
+	ai_charge,	15,	NULL,
+	ai_charge,	15,	NULL
+};
+mmove_t hover_move_end_attack2 = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack2, hover_run};
+
+// end of circle strafe
+
+
+void hover_reattack (edict_t *self)
+{
+	if (self->enemy->health > 0 )
+		if (visible (self, self->enemy) )
+			if (qrandom() <= 0.6)		
+			{
+				if (self->monsterinfo.attack_state == AS_STRAIGHT)
+				{
+					self->monsterinfo.currentmove = &hover_move_attack1;
+					return;
+				}
+				else if (self->monsterinfo.attack_state == AS_SLIDING)
+				{
+					self->monsterinfo.currentmove = &hover_move_attack2;
+					return;
+				}
+				else
+					gi.dprintf ("hover_reattack: unexpected state %d\n", self->monsterinfo.attack_state);
+			}
+	self->monsterinfo.currentmove = &hover_move_end_attack;
+}
+
+
+void hover_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	if (self->s.frame == FRAME_attak104)
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+//PGM	- daedalus fires blaster2
+	if(self->mass < 200)
+		monster_fire_blaster (self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect);
+	else
+		monster_fire_blaster2 (self, start, dir, 1, 1000, MZ2_DAEDALUS_BLASTER, EF_BLASTER);
+		// fixme - different muzzle flash
+//PGM
+}
+
+
+void hover_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &hover_move_stand;
+}
+
+void hover_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &hover_move_stand;
+	else
+		self->monsterinfo.currentmove = &hover_move_run;
+}
+
+void hover_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &hover_move_walk;
+}
+
+void hover_start_attack (edict_t *self)
+{
+	self->monsterinfo.currentmove = &hover_move_start_attack;
+}
+
+void hover_attack(edict_t *self)
+{
+	float chance;
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	// 0% chance of circle in easy
+	// 50% chance in normal
+	// 75% chance in hard
+	// 86.67% chance in nightmare
+	if (!skill->value)
+		chance = 0;
+	else
+		chance = 1.0 - (0.5/(float)(skill->value));
+
+	if (self->mass > 150)  // the daedalus strafes more
+		chance += 0.1;
+
+	if (qrandom() > chance)
+	{
+		self->monsterinfo.currentmove = &hover_move_attack1;
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+	else // circle strafe
+	{
+		if (qrandom () <= 0.5) // switch directions
+			self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+		self->monsterinfo.currentmove = &hover_move_attack2;
+		self->monsterinfo.attack_state = AS_SLIDING;
+	}
+}
+
+
+void hover_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum |= 1;	// PGM support for skins 2 & 3.
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 25)
+	{
+		if (qrandom() < 0.5)
+		{
+			// PMM - daedalus sounds
+			if (self->mass < 225)
+				gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+			else
+				gi.sound (self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain3;
+		}
+		else
+		{
+			// PMM - daedalus sounds
+			if (self->mass < 225)
+				gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+			else
+				gi.sound (self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain2;
+		}
+	}
+	else
+	{
+//====
+//PGM pain sequence is WAY too long
+		if (qrandom() < (0.45 - (0.1 * skill->value)))
+		{
+			// PMM - daedalus sounds
+			if (self->mass < 225)
+				gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+			else
+				gi.sound (self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain1;
+		}
+		else
+		{
+			// PMM - daedalus sounds
+			if (self->mass < 225)
+				gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+			else
+				gi.sound (self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain2;
+		}
+//PGM
+//====
+	}
+}
+
+void hover_deadthink (edict_t *self)
+{
+	if (!self->groundentity && level.time < self->timestamp)
+	{
+		self->nextthink = level.time + FRAMETIME;
+		return;
+	}
+	BecomeExplosion1(self);
+}
+
+void hover_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->think = hover_deadthink;
+	self->nextthink = level.time + FRAMETIME;
+	self->timestamp = level.time + 15;
+	gi.linkentity (self);
+}
+
+void hover_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	self->s.effects = 0;
+	self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	// PMM - daedalus sounds
+	if (self->mass < 225)
+	{
+		if (qrandom() < 0.5)
+			gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		if (qrandom() < 0.5)
+			gi.sound (self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0);
+	}
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &hover_move_death1;
+}
+
+//===========
+//PGM
+qboolean hover_blocked (edict_t *self, float)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+This is the improved icarus monster.
+*/
+void SP_monster_hover (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+	self->health = 240;
+	self->gib_health = -100;
+	self->mass = 150;
+
+	self->pain = hover_pain;
+	self->die = hover_die;
+
+	self->monsterinfo.stand = hover_stand;
+	self->monsterinfo.walk = hover_walk;
+	self->monsterinfo.run = hover_run;
+//	self->monsterinfo.dodge = hover_dodge;
+	self->monsterinfo.attack = hover_start_attack;
+	self->monsterinfo.sight = hover_sight;
+	self->monsterinfo.search = hover_search;
+	self->monsterinfo.blocked = hover_blocked;		// PGM
+
+//PGM
+	if (strcmp(self->classname, "monster_daedalus") == 0)
+	{
+		self->health = 450;
+		self->mass = 225;
+		self->yaw_speed = 25;
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+		self->monsterinfo.power_armor_power = 100;
+		// PMM - daedalus sounds
+		self->s.sound = gi.soundindex ("daedalus/daedidle1.wav");
+		daed_sound_pain1 = gi.soundindex ("daedalus/daedpain1.wav");	
+		daed_sound_pain2 = gi.soundindex ("daedalus/daedpain2.wav");	
+		daed_sound_death1 = gi.soundindex ("daedalus/daeddeth1.wav");	
+		daed_sound_death2 = gi.soundindex ("daedalus/daeddeth2.wav");	
+		daed_sound_sight = gi.soundindex ("daedalus/daedsght1.wav");	
+		daed_sound_search1 = gi.soundindex ("daedalus/daedsrch1.wav");	
+		daed_sound_search2 = gi.soundindex ("daedalus/daedsrch2.wav");	
+		gi.soundindex ("tank/tnkatck3.wav");	
+		// pmm 
+	}
+	else
+	{
+		sound_pain1 = gi.soundindex ("hover/hovpain1.wav");	
+		sound_pain2 = gi.soundindex ("hover/hovpain2.wav");	
+		sound_death1 = gi.soundindex ("hover/hovdeth1.wav");	
+		sound_death2 = gi.soundindex ("hover/hovdeth2.wav");	
+		sound_sight = gi.soundindex ("hover/hovsght1.wav");	
+		sound_search1 = gi.soundindex ("hover/hovsrch1.wav");	
+		sound_search2 = gi.soundindex ("hover/hovsrch2.wav");	
+		gi.soundindex ("hover/hovatck1.wav");	
+
+		self->s.sound = gi.soundindex ("hover/hovidle1.wav");
+	}
+//PGM
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &hover_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+
+//PGM
+	if (strcmp(self->classname, "monster_daedalus") == 0)
+		self->s.skinnum = 2;
+//PGM
+
+}
--- /dev/null
+++ b/rogue/m_hover.h
@@ -1,0 +1,211 @@
+// G:\quake2\baseq2\models/monsters/hover
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_forwrd01        	30
+#define FRAME_forwrd02        	31
+#define FRAME_forwrd03        	32
+#define FRAME_forwrd04        	33
+#define FRAME_forwrd05        	34
+#define FRAME_forwrd06        	35
+#define FRAME_forwrd07        	36
+#define FRAME_forwrd08        	37
+#define FRAME_forwrd09        	38
+#define FRAME_forwrd10        	39
+#define FRAME_forwrd11        	40
+#define FRAME_forwrd12        	41
+#define FRAME_forwrd13        	42
+#define FRAME_forwrd14        	43
+#define FRAME_forwrd15        	44
+#define FRAME_forwrd16        	45
+#define FRAME_forwrd17        	46
+#define FRAME_forwrd18        	47
+#define FRAME_forwrd19        	48
+#define FRAME_forwrd20        	49
+#define FRAME_forwrd21        	50
+#define FRAME_forwrd22        	51
+#define FRAME_forwrd23        	52
+#define FRAME_forwrd24        	53
+#define FRAME_forwrd25        	54
+#define FRAME_forwrd26        	55
+#define FRAME_forwrd27        	56
+#define FRAME_forwrd28        	57
+#define FRAME_forwrd29        	58
+#define FRAME_forwrd30        	59
+#define FRAME_forwrd31        	60
+#define FRAME_forwrd32        	61
+#define FRAME_forwrd33        	62
+#define FRAME_forwrd34        	63
+#define FRAME_forwrd35        	64
+#define FRAME_stop101         	65
+#define FRAME_stop102         	66
+#define FRAME_stop103         	67
+#define FRAME_stop104         	68
+#define FRAME_stop105         	69
+#define FRAME_stop106         	70
+#define FRAME_stop107         	71
+#define FRAME_stop108         	72
+#define FRAME_stop109         	73
+#define FRAME_stop201         	74
+#define FRAME_stop202         	75
+#define FRAME_stop203         	76
+#define FRAME_stop204         	77
+#define FRAME_stop205         	78
+#define FRAME_stop206         	79
+#define FRAME_stop207         	80
+#define FRAME_stop208         	81
+#define FRAME_takeof01        	82
+#define FRAME_takeof02        	83
+#define FRAME_takeof03        	84
+#define FRAME_takeof04        	85
+#define FRAME_takeof05        	86
+#define FRAME_takeof06        	87
+#define FRAME_takeof07        	88
+#define FRAME_takeof08        	89
+#define FRAME_takeof09        	90
+#define FRAME_takeof10        	91
+#define FRAME_takeof11        	92
+#define FRAME_takeof12        	93
+#define FRAME_takeof13        	94
+#define FRAME_takeof14        	95
+#define FRAME_takeof15        	96
+#define FRAME_takeof16        	97
+#define FRAME_takeof17        	98
+#define FRAME_takeof18        	99
+#define FRAME_takeof19        	100
+#define FRAME_takeof20        	101
+#define FRAME_takeof21        	102
+#define FRAME_takeof22        	103
+#define FRAME_takeof23        	104
+#define FRAME_takeof24        	105
+#define FRAME_takeof25        	106
+#define FRAME_takeof26        	107
+#define FRAME_takeof27        	108
+#define FRAME_takeof28        	109
+#define FRAME_takeof29        	110
+#define FRAME_takeof30        	111
+#define FRAME_land01          	112
+#define FRAME_pain101         	113
+#define FRAME_pain102         	114
+#define FRAME_pain103         	115
+#define FRAME_pain104         	116
+#define FRAME_pain105         	117
+#define FRAME_pain106         	118
+#define FRAME_pain107         	119
+#define FRAME_pain108         	120
+#define FRAME_pain109         	121
+#define FRAME_pain110         	122
+#define FRAME_pain111         	123
+#define FRAME_pain112         	124
+#define FRAME_pain113         	125
+#define FRAME_pain114         	126
+#define FRAME_pain115         	127
+#define FRAME_pain116         	128
+#define FRAME_pain117         	129
+#define FRAME_pain118         	130
+#define FRAME_pain119         	131
+#define FRAME_pain120         	132
+#define FRAME_pain121         	133
+#define FRAME_pain122         	134
+#define FRAME_pain123         	135
+#define FRAME_pain124         	136
+#define FRAME_pain125         	137
+#define FRAME_pain126         	138
+#define FRAME_pain127         	139
+#define FRAME_pain128         	140
+#define FRAME_pain201         	141
+#define FRAME_pain202         	142
+#define FRAME_pain203         	143
+#define FRAME_pain204         	144
+#define FRAME_pain205         	145
+#define FRAME_pain206         	146
+#define FRAME_pain207         	147
+#define FRAME_pain208         	148
+#define FRAME_pain209         	149
+#define FRAME_pain210         	150
+#define FRAME_pain211         	151
+#define FRAME_pain212         	152
+#define FRAME_pain301         	153
+#define FRAME_pain302         	154
+#define FRAME_pain303         	155
+#define FRAME_pain304         	156
+#define FRAME_pain305         	157
+#define FRAME_pain306         	158
+#define FRAME_pain307         	159
+#define FRAME_pain308         	160
+#define FRAME_pain309         	161
+#define FRAME_death101        	162
+#define FRAME_death102        	163
+#define FRAME_death103        	164
+#define FRAME_death104        	165
+#define FRAME_death105        	166
+#define FRAME_death106        	167
+#define FRAME_death107        	168
+#define FRAME_death108        	169
+#define FRAME_death109        	170
+#define FRAME_death110        	171
+#define FRAME_death111        	172
+#define FRAME_backwd01        	173
+#define FRAME_backwd02        	174
+#define FRAME_backwd03        	175
+#define FRAME_backwd04        	176
+#define FRAME_backwd05        	177
+#define FRAME_backwd06        	178
+#define FRAME_backwd07        	179
+#define FRAME_backwd08        	180
+#define FRAME_backwd09        	181
+#define FRAME_backwd10        	182
+#define FRAME_backwd11        	183
+#define FRAME_backwd12        	184
+#define FRAME_backwd13        	185
+#define FRAME_backwd14        	186
+#define FRAME_backwd15        	187
+#define FRAME_backwd16        	188
+#define FRAME_backwd17        	189
+#define FRAME_backwd18        	190
+#define FRAME_backwd19        	191
+#define FRAME_backwd20        	192
+#define FRAME_backwd21        	193
+#define FRAME_backwd22        	194
+#define FRAME_backwd23        	195
+#define FRAME_backwd24        	196
+#define FRAME_attak101        	197
+#define FRAME_attak102        	198
+#define FRAME_attak103        	199
+#define FRAME_attak104        	200
+#define FRAME_attak105        	201
+#define FRAME_attak106        	202
+#define FRAME_attak107        	203
+#define FRAME_attak108        	204
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_infantry.c
@@ -1,0 +1,818 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_infantry.h"
+
+void InfantryMachineGun (edict_t *self);
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die1;
+static int	sound_die2;
+
+static int	sound_gunshot;
+static int	sound_weapon_cock;
+static int	sound_punch_swing;
+static int	sound_punch_hit;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_idle;
+
+mframe_t infantry_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t infantry_move_stand = {FRAME_stand50, FRAME_stand71, infantry_frames_stand, NULL};
+
+void infantry_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_stand;
+}
+
+
+mframe_t infantry_frames_fidget [] =
+{
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 3,  NULL,
+	ai_stand, 6,  NULL,
+	ai_stand, 3,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -2, NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -2, NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -2, NULL
+};
+mmove_t infantry_move_fidget = {FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand};
+
+void infantry_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_fidget;
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t infantry_frames_walk [] =
+{
+	ai_walk, 5,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL
+};
+mmove_t infantry_move_walk = {FRAME_walk03, FRAME_walk14, infantry_frames_walk, NULL};
+
+void infantry_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_walk;
+}
+
+mframe_t infantry_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 20, NULL,
+	ai_run, 5,  NULL,
+	ai_run, 7,  monster_done_dodge,
+	ai_run, 30, NULL,
+	ai_run, 35, NULL,
+	ai_run, 2,  NULL,
+	ai_run, 6,  NULL
+};
+mmove_t infantry_move_run = {FRAME_run01, FRAME_run08, infantry_frames_run, NULL};
+
+void infantry_run (edict_t *self)
+{
+	monster_done_dodge (self);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &infantry_move_stand;
+	else
+		self->monsterinfo.currentmove = &infantry_move_run;
+}
+
+
+mframe_t infantry_frames_pain1 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, -2, NULL,
+	ai_move, -1, NULL,
+	ai_move, -2, NULL,
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, 6,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t infantry_move_pain1 = {FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run};
+
+mframe_t infantry_frames_pain2 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, -3, NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -2, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 5,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t infantry_move_pain2 = {FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run};
+
+void infantry_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (!self->groundentity)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("infantry: pain avoided due to no ground\n");
+		return;
+	}
+
+	monster_done_dodge (self);
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = rand() % 2;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &infantry_move_pain1;
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &infantry_move_pain2;
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	}
+
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+}
+
+
+vec3_t	aimangles[] =
+{
+	0.0, 5.0, 0.0,
+	10.0, 15.0, 0.0,
+	20.0, 25.0, 0.0,
+	25.0, 35.0, 0.0,
+	30.0, 40.0, 0.0,
+	30.0, 45.0, 0.0,
+	25.0, 50.0, 0.0,
+	20.0, 40.0, 0.0,
+	15.0, 35.0, 0.0,
+	40.0, 35.0, 0.0,
+	70.0, 35.0, 0.0,
+	90.0, 35.0, 0.0
+};
+
+void InfantryMachineGun (edict_t *self)
+{
+	vec3_t	start, target;
+	vec3_t	forward, right;
+	vec3_t	vec;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	// pmm - new attack start frame
+	if (self->s.frame == FRAME_attak104)
+	{
+		flash_number = MZ2_INFANTRY_MACHINEGUN_1;
+		AngleVectors (self->s.angles, forward, right, NULL);
+		G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+		if (self->enemy)
+		{
+			VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+			target[2] += self->enemy->viewheight;
+			VectorSubtract (target, start, forward);
+			VectorNormalize (forward);
+		}
+		else
+		{
+			AngleVectors (self->s.angles, forward, right, NULL);
+		}
+	}
+	else
+	{
+		flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211);
+
+		AngleVectors (self->s.angles, forward, right, NULL);
+		G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+		VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec);
+		AngleVectors (vec, forward, NULL, NULL);
+	}
+
+	monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void infantry_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void infantry_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	gi.linkentity (self);
+
+	M_FlyCheck (self);
+}
+
+mframe_t infantry_frames_death1 [] =
+{
+	ai_move, -4, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -4, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, -2, NULL,
+	ai_move, 2,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 9,  NULL,
+	ai_move, 9,  NULL,
+	ai_move, 5,  NULL,
+	ai_move, -3, NULL,
+	ai_move, -3, NULL
+};
+mmove_t infantry_move_death1 = {FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead};
+
+// Off with his head
+mframe_t infantry_frames_death2 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 5,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, -3,  InfantryMachineGun,
+	ai_move, -1,  InfantryMachineGun,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, 0,   InfantryMachineGun,
+	ai_move, 2,   InfantryMachineGun,
+	ai_move, 2,   InfantryMachineGun,
+	ai_move, 3,   InfantryMachineGun,
+	ai_move, -10, InfantryMachineGun,
+	ai_move, -7,  InfantryMachineGun,
+	ai_move, -8,  InfantryMachineGun,
+	ai_move, -6,  NULL,
+	ai_move, 4,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t infantry_move_death2 = {FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead};
+
+mframe_t infantry_frames_death3 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -6,  NULL,
+	ai_move, -11, NULL,
+	ai_move, -3,  NULL,
+	ai_move, -11, NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead};
+
+
+void infantry_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 3;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &infantry_move_death1;
+		gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+	}
+	else if (n == 1)
+	{
+		self->monsterinfo.currentmove = &infantry_move_death2;
+		gi.sound (self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &infantry_move_death3;
+		gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+	}
+}
+
+mframe_t infantry_frames_duck [] =
+{
+	ai_move, -2, monster_duck_down,
+	ai_move, -5, monster_duck_hold,
+	ai_move, 3,  NULL,
+	ai_move, 4,  monster_duck_up,
+	ai_move, 0,  NULL
+};
+mmove_t infantry_move_duck = {FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run};
+
+// PMM - dodge code moved below so I can see the attack frames
+
+
+void infantry_cock_gun (edict_t *self)
+{
+	// pmm .. code that was here no longer needed
+	gi.sound (self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0);
+}
+
+void infantry_fire (edict_t *self)
+{
+	InfantryMachineGun (self);
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+// this is here instead of cock_gun
+
+void infantry_fire_prep (edict_t *self)
+{
+	int n;
+	n = (rand() & 15) + 3 + 1;
+	self->monsterinfo.pausetime = level.time + n*FRAMETIME;
+}
+
+// pmm
+// frames reordered, tweaked for new frames
+
+mframe_t infantry_frames_attack1 [] =
+{
+	ai_charge, -3, NULL,					//101
+	ai_charge, -2, NULL,					//102
+	ai_charge, -1, infantry_fire_prep,		//103
+	ai_charge, 5,  infantry_fire,			//104
+	ai_charge, 1,  NULL,					//105
+	ai_charge, -3, NULL,					//106
+	ai_charge, -2, NULL,					//107
+	ai_charge, 2,  infantry_cock_gun,		//108
+	ai_charge, 1,  NULL,					//109
+	ai_charge, 1,  NULL,					//110
+	ai_charge, -1, NULL,					//111
+	ai_charge, 0,  NULL,					//112
+	ai_charge, -1, NULL,					//113
+	ai_charge, -1, NULL,					//114
+	ai_charge, 4,  NULL						//115
+};
+mmove_t infantry_move_attack1 = {FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run};
+
+
+void infantry_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0);
+}
+
+void infantry_smack (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	if (fire_hit (self, aim, (5 + (rand() % 5)), 50))
+		gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0);
+}
+
+mframe_t infantry_frames_attack2 [] =
+{
+	ai_charge, 3, NULL,
+	ai_charge, 6, NULL,
+	ai_charge, 0, infantry_swing,
+	ai_charge, 8, NULL,
+	ai_charge, 5, NULL,
+	ai_charge, 8, infantry_smack,
+	ai_charge, 6, NULL,
+	ai_charge, 3, NULL,
+};
+mmove_t infantry_move_attack2 = {FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run};
+
+void infantry_attack(edict_t *self)
+{
+	monster_done_dodge (self);
+
+	if (range (self, self->enemy) == RANGE_MELEE)
+		self->monsterinfo.currentmove = &infantry_move_attack2;
+	else
+		self->monsterinfo.currentmove = &infantry_move_attack1;
+}
+
+//===========
+//PGM
+void infantry_jump_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void infantry_jump2_now (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 150, forward, self->velocity);
+	VectorMA(self->velocity, 400, up, self->velocity);
+}
+
+void infantry_jump_wait_land (edict_t *self)
+{
+	if(self->groundentity == NULL)
+	{
+		self->monsterinfo.nextframe = self->s.frame;
+
+		if(monster_jump_finished (self))
+			self->monsterinfo.nextframe = self->s.frame + 1;
+	}
+	else 
+		self->monsterinfo.nextframe = self->s.frame + 1;
+}
+
+mframe_t infantry_frames_jump [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, infantry_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, infantry_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t infantry_move_jump = { FRAME_jump01, FRAME_jump10, infantry_frames_jump, infantry_run };
+
+mframe_t infantry_frames_jump2 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -4, NULL,
+	ai_move, -4, NULL,
+	ai_move, 0, infantry_jump_now,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, infantry_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t infantry_move_jump2 = { FRAME_jump01, FRAME_jump10, infantry_frames_jump2, infantry_run };
+
+void infantry_jump (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	monster_done_dodge(self);
+
+	if(self->enemy->s.origin[2] > self->s.origin[2])
+		self->monsterinfo.currentmove = &infantry_move_jump2;
+	else
+		self->monsterinfo.currentmove = &infantry_move_jump;
+}
+
+qboolean infantry_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkjump (self, dist, 192, 40))
+	{
+		infantry_jump(self);
+		return true;
+	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+/*
+void infantry_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+//===========
+//PMM - rogue rewrite of gunner dodge code.
+	float	r;
+	float	height;
+	int		shooting = 0;
+
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	r = qrandom();
+	if (r > (0.25*((skill->value)+1)))
+		return;
+
+	if ((self->monsterinfo.currentmove == &infantry_move_attack1) ||
+		(self->monsterinfo.currentmove == &infantry_move_attack2))
+	{
+		shooting = 1;
+	}
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		height = self->absmax[2];
+	}
+	else
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+	}
+
+	// check to see if it makes sense to duck
+	if (tr->endpos[2] <= height)
+	{
+		vec3_t right,diff;
+
+		if (shooting)
+		{
+			self->monsterinfo.attack_state = AS_SLIDING;
+			return;
+		}
+		AngleVectors (self->s.angles, NULL, right, NULL);
+		VectorSubtract (tr->endpos, self->s.origin, diff);
+		if (DotProduct (right, diff) < 0)
+		{
+			self->monsterinfo.lefty = 1;
+		}
+		// if it doesn't sense to duck, try to strafe away
+		monster_done_dodge (self);
+		self->monsterinfo.currentmove = &infantry_move_run;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		return;
+	}
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &infantry_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.aiflags |= AI_DODGING;
+		return;
+	}
+
+	if (!shooting)
+	{
+		self->monsterinfo.currentmove = &infantry_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+		self->monsterinfo.aiflags |= AI_DODGING;
+	}
+	return;
+//PMM
+//===========
+*/
+
+void infantry_duck (edict_t *self, float eta)
+{
+	// if we're jumping, don't dodge
+	if ((self->monsterinfo.currentmove == &infantry_move_jump) ||
+		(self->monsterinfo.currentmove == &infantry_move_jump2))
+	{
+		return;
+	}
+
+	if ((self->monsterinfo.currentmove == &infantry_move_attack1) ||
+		(self->monsterinfo.currentmove == &infantry_move_attack2))
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DUCKED;
+			return;
+		}
+	}
+
+	if (skill->value == 0)
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	else
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+
+	// has to be done immediately otherwise he can get stuck
+	monster_duck_down(self);
+
+	self->monsterinfo.nextframe = FRAME_duck01;
+	self->monsterinfo.currentmove = &infantry_move_duck;
+	return;
+}
+
+void infantry_sidestep (edict_t *self)
+{
+	// if we're jumping, don't dodge
+	if ((self->monsterinfo.currentmove == &infantry_move_jump) ||
+		(self->monsterinfo.currentmove == &infantry_move_jump2))
+	{
+		return;
+	}
+
+	if ((self->monsterinfo.currentmove == &infantry_move_attack1) ||
+		(self->monsterinfo.currentmove == &infantry_move_attack2))
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DODGING;
+			return;
+		}
+	}
+
+	if (self->monsterinfo.currentmove != &infantry_move_run)
+		self->monsterinfo.currentmove = &infantry_move_run;
+}
+
+/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_infantry (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("infantry/infpain1.wav");
+	sound_pain2 = gi.soundindex ("infantry/infpain2.wav");
+	sound_die1 = gi.soundindex ("infantry/infdeth1.wav");
+	sound_die2 = gi.soundindex ("infantry/infdeth2.wav");
+
+	sound_gunshot = gi.soundindex ("infantry/infatck1.wav");
+	sound_weapon_cock = gi.soundindex ("infantry/infatck3.wav");
+	sound_punch_swing = gi.soundindex ("infantry/infatck2.wav");
+	sound_punch_hit = gi.soundindex ("infantry/melee2.wav");
+	
+	sound_sight = gi.soundindex ("infantry/infsght1.wav");
+	sound_search = gi.soundindex ("infantry/infsrch1.wav");
+	sound_idle = gi.soundindex ("infantry/infidle1.wav");
+	
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = -40;
+	self->mass = 200;
+
+	self->pain = infantry_pain;
+	self->die = infantry_die;
+
+	self->monsterinfo.stand = infantry_stand;
+	self->monsterinfo.walk = infantry_walk;
+	self->monsterinfo.run = infantry_run;
+	// pmm
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.duck = infantry_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+	self->monsterinfo.sidestep = infantry_sidestep;
+//	self->monsterinfo.dodge = infantry_dodge;
+	// pmm
+	self->monsterinfo.attack = infantry_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = infantry_sight;
+	self->monsterinfo.idle = infantry_fidget;
+	self->monsterinfo.blocked = infantry_blocked;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &infantry_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_infantry.h
@@ -1,0 +1,226 @@
+// G:\quake2\baseq2\models/monsters/infantry
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_gun02           	0
+#define FRAME_stand01         	1
+#define FRAME_stand02         	2
+#define FRAME_stand03         	3
+#define FRAME_stand04         	4
+#define FRAME_stand05         	5
+#define FRAME_stand06         	6
+#define FRAME_stand07         	7
+#define FRAME_stand08         	8
+#define FRAME_stand09         	9
+#define FRAME_stand10         	10
+#define FRAME_stand11         	11
+#define FRAME_stand12         	12
+#define FRAME_stand13         	13
+#define FRAME_stand14         	14
+#define FRAME_stand15         	15
+#define FRAME_stand16         	16
+#define FRAME_stand17         	17
+#define FRAME_stand18         	18
+#define FRAME_stand19         	19
+#define FRAME_stand20         	20
+#define FRAME_stand21         	21
+#define FRAME_stand22         	22
+#define FRAME_stand23         	23
+#define FRAME_stand24         	24
+#define FRAME_stand25         	25
+#define FRAME_stand26         	26
+#define FRAME_stand27         	27
+#define FRAME_stand28         	28
+#define FRAME_stand29         	29
+#define FRAME_stand30         	30
+#define FRAME_stand31         	31
+#define FRAME_stand32         	32
+#define FRAME_stand33         	33
+#define FRAME_stand34         	34
+#define FRAME_stand35         	35
+#define FRAME_stand36         	36
+#define FRAME_stand37         	37
+#define FRAME_stand38         	38
+#define FRAME_stand39         	39
+#define FRAME_stand40         	40
+#define FRAME_stand41         	41
+#define FRAME_stand42         	42
+#define FRAME_stand43         	43
+#define FRAME_stand44         	44
+#define FRAME_stand45         	45
+#define FRAME_stand46         	46
+#define FRAME_stand47         	47
+#define FRAME_stand48         	48
+#define FRAME_stand49         	49
+#define FRAME_stand50         	50
+#define FRAME_stand51         	51
+#define FRAME_stand52         	52
+#define FRAME_stand53         	53
+#define FRAME_stand54         	54
+#define FRAME_stand55         	55
+#define FRAME_stand56         	56
+#define FRAME_stand57         	57
+#define FRAME_stand58         	58
+#define FRAME_stand59         	59
+#define FRAME_stand60         	60
+#define FRAME_stand61         	61
+#define FRAME_stand62         	62
+#define FRAME_stand63         	63
+#define FRAME_stand64         	64
+#define FRAME_stand65         	65
+#define FRAME_stand66         	66
+#define FRAME_stand67         	67
+#define FRAME_stand68         	68
+#define FRAME_stand69         	69
+#define FRAME_stand70         	70
+#define FRAME_stand71         	71
+#define FRAME_walk01          	72
+#define FRAME_walk02          	73
+#define FRAME_walk03          	74
+#define FRAME_walk04          	75
+#define FRAME_walk05          	76
+#define FRAME_walk06          	77
+#define FRAME_walk07          	78
+#define FRAME_walk08          	79
+#define FRAME_walk09          	80
+#define FRAME_walk10          	81
+#define FRAME_walk11          	82
+#define FRAME_walk12          	83
+#define FRAME_walk13          	84
+#define FRAME_walk14          	85
+#define FRAME_walk15          	86
+#define FRAME_walk16          	87
+#define FRAME_walk17          	88
+#define FRAME_walk18          	89
+#define FRAME_walk19          	90
+#define FRAME_walk20          	91
+#define FRAME_run01           	92
+#define FRAME_run02           	93
+#define FRAME_run03           	94
+#define FRAME_run04           	95
+#define FRAME_run05           	96
+#define FRAME_run06           	97
+#define FRAME_run07           	98
+#define FRAME_run08           	99
+#define FRAME_pain101         	100
+#define FRAME_pain102         	101
+#define FRAME_pain103         	102
+#define FRAME_pain104         	103
+#define FRAME_pain105         	104
+#define FRAME_pain106         	105
+#define FRAME_pain107         	106
+#define FRAME_pain108         	107
+#define FRAME_pain109         	108
+#define FRAME_pain110         	109
+#define FRAME_pain201         	110
+#define FRAME_pain202         	111
+#define FRAME_pain203         	112
+#define FRAME_pain204         	113
+#define FRAME_pain205         	114
+#define FRAME_pain206         	115
+#define FRAME_pain207         	116
+#define FRAME_pain208         	117
+#define FRAME_pain209         	118
+#define FRAME_pain210         	119
+#define FRAME_duck01          	120
+#define FRAME_duck02          	121
+#define FRAME_duck03          	122
+#define FRAME_duck04          	123
+#define FRAME_duck05          	124
+#define FRAME_death101        	125
+#define FRAME_death102        	126
+#define FRAME_death103        	127
+#define FRAME_death104        	128
+#define FRAME_death105        	129
+#define FRAME_death106        	130
+#define FRAME_death107        	131
+#define FRAME_death108        	132
+#define FRAME_death109        	133
+#define FRAME_death110        	134
+#define FRAME_death111        	135
+#define FRAME_death112        	136
+#define FRAME_death113        	137
+#define FRAME_death114        	138
+#define FRAME_death115        	139
+#define FRAME_death116        	140
+#define FRAME_death117        	141
+#define FRAME_death118        	142
+#define FRAME_death119        	143
+#define FRAME_death120        	144
+#define FRAME_death201        	145
+#define FRAME_death202        	146
+#define FRAME_death203        	147
+#define FRAME_death204        	148
+#define FRAME_death205        	149
+#define FRAME_death206        	150
+#define FRAME_death207        	151
+#define FRAME_death208        	152
+#define FRAME_death209        	153
+#define FRAME_death210        	154
+#define FRAME_death211        	155
+#define FRAME_death212        	156
+#define FRAME_death213        	157
+#define FRAME_death214        	158
+#define FRAME_death215        	159
+#define FRAME_death216        	160
+#define FRAME_death217        	161
+#define FRAME_death218        	162
+#define FRAME_death219        	163
+#define FRAME_death220        	164
+#define FRAME_death221        	165
+#define FRAME_death222        	166
+#define FRAME_death223        	167
+#define FRAME_death224        	168
+#define FRAME_death225        	169
+#define FRAME_death301        	170
+#define FRAME_death302        	171
+#define FRAME_death303        	172
+#define FRAME_death304        	173
+#define FRAME_death305        	174
+#define FRAME_death306        	175
+#define FRAME_death307        	176
+#define FRAME_death308        	177
+#define FRAME_death309        	178
+#define FRAME_block01         	179
+#define FRAME_block02         	180
+#define FRAME_block03         	181
+#define FRAME_block04         	182
+#define FRAME_block05         	183
+#define FRAME_attak101        	184
+#define FRAME_attak102        	185
+#define FRAME_attak103        	186
+#define FRAME_attak104        	187
+#define FRAME_attak105        	188
+#define FRAME_attak106        	189
+#define FRAME_attak107        	190
+#define FRAME_attak108        	191
+#define FRAME_attak109        	192
+#define FRAME_attak110        	193
+#define FRAME_attak111        	194
+#define FRAME_attak112        	195
+#define FRAME_attak113        	196
+#define FRAME_attak114        	197
+#define FRAME_attak115        	198
+#define FRAME_attak201        	199
+#define FRAME_attak202        	200
+#define FRAME_attak203        	201
+#define FRAME_attak204        	202
+#define FRAME_attak205        	203
+#define FRAME_attak206        	204
+#define FRAME_attak207        	205
+#define FRAME_attak208        	206
+
+//PGM
+#define FRAME_jump01			207
+#define FRAME_jump02			208
+#define FRAME_jump03			209
+#define FRAME_jump04			210
+#define FRAME_jump05			211
+#define FRAME_jump06			212
+#define FRAME_jump07			213
+#define FRAME_jump08			214
+#define FRAME_jump09			215
+#define FRAME_jump10			216
+//PGM
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_insane.c
@@ -1,0 +1,670 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_insane.h"
+
+
+static int	sound_fist;
+static int	sound_shake;
+static int	sound_moan;
+static int	sound_scream[8];
+
+void insane_fist (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0);
+}
+
+void insane_shake (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0);
+}
+
+void insane_moan (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0);
+}
+
+void insane_scream (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_scream[rand()%8], 1, ATTN_IDLE, 0);
+}
+
+
+void insane_stand (edict_t *self);
+void insane_dead (edict_t *self);
+void insane_cross (edict_t *self);
+void insane_walk (edict_t *self);
+void insane_run (edict_t *self);
+void insane_checkdown (edict_t *self);
+void insane_checkup (edict_t *self);
+void insane_onground (edict_t *self);
+
+
+mframe_t insane_frames_stand_normal [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, insane_checkdown
+};
+mmove_t insane_move_stand_normal = {FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand};
+
+mframe_t insane_frames_stand_insane [] =
+{
+	ai_stand,	0,	insane_shake,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	insane_checkdown
+};
+mmove_t insane_move_stand_insane = {FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand};
+
+mframe_t insane_frames_uptodown [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_moan,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	2.7,	NULL,
+	ai_move,	4.1,	NULL,
+	ai_move,	6,		NULL,
+	ai_move,	7.6,	NULL,
+	ai_move,	3.6,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_fist,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_fist,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t insane_move_uptodown = {FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground};
+
+
+mframe_t insane_frames_downtoup [] =
+{
+	ai_move,	-0.7,	NULL,			// 41
+	ai_move,	-1.2,	NULL,			// 42
+	ai_move,	-1.5,		NULL,		// 43
+	ai_move,	-4.5,		NULL,		// 44
+	ai_move,	-3.5,	NULL,			// 45
+	ai_move,	-0.2,	NULL,			// 46
+	ai_move,	0,	NULL,			// 47
+	ai_move,	-1.3,	NULL,			// 48
+	ai_move,	-3,	NULL,				// 49
+	ai_move,	-2,	NULL,			// 50
+	ai_move,	0,	NULL,				// 51
+	ai_move,	0,	NULL,				// 52
+	ai_move,	0,	NULL,				// 53
+	ai_move,	-3.3,	NULL,			// 54
+	ai_move,	-1.6,	NULL,			// 55
+	ai_move,	-0.3,	NULL,			// 56
+	ai_move,	0,	NULL,				// 57
+	ai_move,	0,	NULL,				// 58
+	ai_move,	0,	NULL				// 59
+};
+mmove_t insane_move_downtoup = {FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand};
+
+mframe_t insane_frames_jumpdown [] =
+{
+	ai_move,	0.2,	NULL,
+	ai_move,	11.5,	NULL,
+	ai_move,	5.1,	NULL,
+	ai_move,	7.1,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t insane_move_jumpdown = {FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground};
+
+
+mframe_t insane_frames_down [] =
+{
+	ai_move,	0,		NULL,		// 100
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 110
+	ai_move,	-1.7,		NULL,
+	ai_move,	-1.6,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		insane_fist,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 120
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 130
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		insane_moan,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 140
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 150
+	ai_move,	0.5,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	-0.2,		insane_scream,
+	ai_move,	0,		NULL,
+	ai_move,	0.2,		NULL,
+	ai_move,	0.4,		NULL,
+	ai_move,	0.6,		NULL,
+	ai_move,	0.8,		NULL,
+	ai_move,	0.7,		NULL,
+	ai_move,	0,		insane_checkup		// 160
+};
+mmove_t insane_move_down = {FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground};
+
+mframe_t insane_frames_walk_normal [] =
+{
+	ai_walk,	0,		insane_scream,
+	ai_walk,	2.5,	NULL,
+	ai_walk,	3.5,	NULL,
+	ai_walk,	1.7,	NULL,
+	ai_walk,	2.3,	NULL,
+	ai_walk,	2.4,	NULL,
+	ai_walk,	2.2,	NULL,
+	ai_walk,	4.2,	NULL,
+	ai_walk,	5.6,	NULL,
+	ai_walk,	3.3,	NULL,
+	ai_walk,	2.4,	NULL,
+	ai_walk,	0.9,	NULL,
+	ai_walk,	0,		NULL
+};
+mmove_t insane_move_walk_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk};
+mmove_t insane_move_run_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run};
+
+mframe_t insane_frames_walk_insane [] =
+{
+	ai_walk,	0,		insane_scream,		// walk 1
+	ai_walk,	3.4,	NULL,		// walk 2
+	ai_walk,	3.6,	NULL,		// 3
+	ai_walk,	2.9,	NULL,		// 4
+	ai_walk,	2.2,	NULL,		// 5
+	ai_walk,	2.6,	NULL,		// 6
+	ai_walk,	0,		NULL,		// 7
+	ai_walk,	0.7,	NULL,		// 8
+	ai_walk,	4.8,	NULL,		// 9
+	ai_walk,	5.3,	NULL,		// 10
+	ai_walk,	1.1,	NULL,		// 11
+	ai_walk,	2,		NULL,		// 12
+	ai_walk,	0.5,	NULL,		// 13
+	ai_walk,	0,		NULL,		// 14
+	ai_walk,	0,		NULL,		// 15
+	ai_walk,	4.9,	NULL,		// 16
+	ai_walk,	6.7,	NULL,		// 17
+	ai_walk,	3.8,	NULL,		// 18
+	ai_walk,	2,		NULL,		// 19
+	ai_walk,	0.2,	NULL,		// 20
+	ai_walk,	0,		NULL,		// 21
+	ai_walk,	3.4,	NULL,		// 22
+	ai_walk,	6.4,	NULL,		// 23
+	ai_walk,	5,		NULL,		// 24
+	ai_walk,	1.8,	NULL,		// 25
+	ai_walk,	0,		NULL		// 26
+};
+mmove_t insane_move_walk_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk};
+mmove_t insane_move_run_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run};
+
+mframe_t insane_frames_stand_pain [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_stand_pain = {FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run};
+
+mframe_t insane_frames_stand_death [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_stand_death = {FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead};
+
+mframe_t insane_frames_crawl [] =
+{
+	ai_walk,	0,		insane_scream,
+	ai_walk,	1.5,	NULL,
+	ai_walk,	2.1,	NULL,
+	ai_walk,	3.6,	NULL,
+	ai_walk,	2,		NULL,
+	ai_walk,	0.9,	NULL,
+	ai_walk,	3,		NULL,
+	ai_walk,	3.4,	NULL,
+	ai_walk,	2.4,	NULL
+};
+mmove_t insane_move_crawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL};
+mmove_t insane_move_runcrawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL};
+
+mframe_t insane_frames_crawl_pain [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_crawl_pain = {FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run};
+
+mframe_t insane_frames_crawl_death [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_crawl_death = {FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead};
+
+mframe_t insane_frames_cross [] =
+{
+	ai_move,	0,		insane_moan,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_cross = {FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross};
+
+mframe_t insane_frames_struggle_cross [] =
+{
+	ai_move,	0,		insane_scream,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_struggle_cross = {FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross};
+
+void insane_cross (edict_t *self)
+{
+	if (qrandom() < 0.8)		
+		self->monsterinfo.currentmove = &insane_move_cross;
+	else
+		self->monsterinfo.currentmove = &insane_move_struggle_cross;
+}
+
+void insane_walk (edict_t *self)
+{
+	if ( self->spawnflags & 16 )			// Hold Ground?
+		if (self->s.frame == FRAME_cr_pain10)
+		{
+			self->monsterinfo.currentmove = &insane_move_down;
+			return;
+		}
+	if (self->spawnflags & 4)
+		self->monsterinfo.currentmove = &insane_move_crawl;
+	else
+		if (qrandom() <= 0.5)
+			self->monsterinfo.currentmove = &insane_move_walk_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_walk_insane;
+}
+
+void insane_run (edict_t *self)
+{
+	if ( self->spawnflags & 16 )			// Hold Ground?
+		if (self->s.frame == FRAME_cr_pain10)
+		{
+			self->monsterinfo.currentmove = &insane_move_down;
+			return;
+		}
+	if (self->spawnflags & 4)				// Crawling?
+		self->monsterinfo.currentmove = &insane_move_runcrawl;
+	else
+		if (qrandom() <= 0.5)				// Else, mix it up
+			self->monsterinfo.currentmove = &insane_move_run_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_run_insane;
+}
+
+
+void insane_pain (edict_t *self, edict_t *, float, int)
+{
+	int	l,r;
+
+//	if (self->health < (self->max_health / 2))
+//		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	r = 1 + (rand()&1);
+	if (self->health < 25)
+		l = 25;
+	else if (self->health < 50)
+		l = 50;
+	else if (self->health < 75)
+		l = 75;
+	else
+		l = 100;
+	gi.sound (self, CHAN_VOICE, gi.soundindex (va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	// Don't go into pain frames if crucified.
+	if (self->spawnflags & 8)
+	{
+		self->monsterinfo.currentmove = &insane_move_struggle_cross;			
+		return;
+	}
+	
+	if  ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) )
+	{
+		self->monsterinfo.currentmove = &insane_move_crawl_pain;
+	}
+	else
+		self->monsterinfo.currentmove = &insane_move_stand_pain;
+
+}
+
+void insane_onground (edict_t *self)
+{
+	self->monsterinfo.currentmove = &insane_move_down;
+}
+
+void insane_checkdown (edict_t *self)
+{
+//	if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) )
+	if (self->spawnflags & 32)				// Always stand
+		return;
+	if (qrandom() < 0.3)
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &insane_move_uptodown;
+		else
+			self->monsterinfo.currentmove = &insane_move_jumpdown; 
+}
+
+void insane_checkup (edict_t *self)
+{
+	// If Hold_Ground and Crawl are set
+	if ( (self->spawnflags & 4) && (self->spawnflags & 16) )
+		return;
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &insane_move_downtoup;				
+
+}
+
+void insane_stand (edict_t *self)
+{
+	if (self->spawnflags & 8)			// If crucified
+	{
+		self->monsterinfo.currentmove = &insane_move_cross;
+		self->monsterinfo.aiflags |= AI_STAND_GROUND;
+	}
+	// If Hold_Ground and Crawl are set
+	else if ( (self->spawnflags & 4) && (self->spawnflags & 16) )
+		self->monsterinfo.currentmove = &insane_move_down;
+	else
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &insane_move_stand_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_stand_insane;
+}
+
+void insane_dead (edict_t *self)
+{
+	if (self->spawnflags & 8)
+	{
+		self->flags |= FL_FLY;
+	}
+	else
+	{
+		VectorSet (self->mins, -16, -16, -24);
+		VectorSet (self->maxs, 16, 16, -8);
+		self->movetype = MOVETYPE_TOSS;
+	}
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void insane_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_IDLE, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand()%4)+1)), 1, ATTN_IDLE, 0);
+
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	if (self->spawnflags & 8)
+	{
+		insane_dead (self);
+	}
+	else
+	{
+		if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) )		
+			self->monsterinfo.currentmove = &insane_move_crawl_death;
+		else
+			self->monsterinfo.currentmove = &insane_move_stand_death;
+	}
+}
+
+
+/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND
+*/
+void SP_misc_insane (edict_t *self)
+{
+//	static int skin = 0;	//@@
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_fist = gi.soundindex ("insane/insane11.wav");
+	sound_shake = gi.soundindex ("insane/insane5.wav");
+	sound_moan = gi.soundindex ("insane/insane7.wav");
+	sound_scream[0] = gi.soundindex ("insane/insane1.wav");
+	sound_scream[1] = gi.soundindex ("insane/insane2.wav");
+	sound_scream[2] = gi.soundindex ("insane/insane3.wav");
+	sound_scream[3] = gi.soundindex ("insane/insane4.wav");
+	sound_scream[4] = gi.soundindex ("insane/insane6.wav");
+	sound_scream[5] = gi.soundindex ("insane/insane8.wav");
+	sound_scream[6] = gi.soundindex ("insane/insane9.wav");
+	sound_scream[7] = gi.soundindex ("insane/insane10.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2");
+
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = -50;
+	self->mass = 300;
+
+	self->pain = insane_pain;
+	self->die = insane_die;
+
+	self->monsterinfo.stand = insane_stand;
+	self->monsterinfo.walk = insane_walk;
+	self->monsterinfo.run = insane_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = NULL;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+	self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+//@@
+//	self->s.skinnum = skin;
+//	skin++;
+//	if (skin > 12)
+//		skin = 0;
+
+	gi.linkentity (self);
+
+	if (self->spawnflags & 16)				// Stand Ground
+		self->monsterinfo.aiflags |= AI_STAND_GROUND;
+
+	self->monsterinfo.currentmove = &insane_move_stand_normal;
+	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	if (self->spawnflags & 8)					// Crucified ?
+	{
+		VectorSet (self->mins, -16, 0, 0);
+		VectorSet (self->maxs, 16, 8, 32);
+		self->flags |= FL_NO_KNOCKBACK;
+		flymonster_start (self);
+	}
+	else
+	{
+		walkmonster_start (self);
+		self->s.skinnum = rand()%3;
+	}
+}
--- /dev/null
+++ b/rogue/m_insane.h
@@ -1,0 +1,288 @@
+// G:\quake2\baseq2\models/monsters/insane
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_stand6          	5
+#define FRAME_stand7          	6
+#define FRAME_stand8          	7
+#define FRAME_stand9          	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_stand41         	40
+#define FRAME_stand42         	41
+#define FRAME_stand43         	42
+#define FRAME_stand44         	43
+#define FRAME_stand45         	44
+#define FRAME_stand46         	45
+#define FRAME_stand47         	46
+#define FRAME_stand48         	47
+#define FRAME_stand49         	48
+#define FRAME_stand50         	49
+#define FRAME_stand51         	50
+#define FRAME_stand52         	51
+#define FRAME_stand53         	52
+#define FRAME_stand54         	53
+#define FRAME_stand55         	54
+#define FRAME_stand56         	55
+#define FRAME_stand57         	56
+#define FRAME_stand58         	57
+#define FRAME_stand59         	58
+#define FRAME_stand60         	59
+#define FRAME_stand61         	60
+#define FRAME_stand62         	61
+#define FRAME_stand63         	62
+#define FRAME_stand64         	63
+#define FRAME_stand65         	64
+#define FRAME_stand66         	65
+#define FRAME_stand67         	66
+#define FRAME_stand68         	67
+#define FRAME_stand69         	68
+#define FRAME_stand70         	69
+#define FRAME_stand71         	70
+#define FRAME_stand72         	71
+#define FRAME_stand73         	72
+#define FRAME_stand74         	73
+#define FRAME_stand75         	74
+#define FRAME_stand76         	75
+#define FRAME_stand77         	76
+#define FRAME_stand78         	77
+#define FRAME_stand79         	78
+#define FRAME_stand80         	79
+#define FRAME_stand81         	80
+#define FRAME_stand82         	81
+#define FRAME_stand83         	82
+#define FRAME_stand84         	83
+#define FRAME_stand85         	84
+#define FRAME_stand86         	85
+#define FRAME_stand87         	86
+#define FRAME_stand88         	87
+#define FRAME_stand89         	88
+#define FRAME_stand90         	89
+#define FRAME_stand91         	90
+#define FRAME_stand92         	91
+#define FRAME_stand93         	92
+#define FRAME_stand94         	93
+#define FRAME_stand95         	94
+#define FRAME_stand96         	95
+#define FRAME_stand97         	96
+#define FRAME_stand98         	97
+#define FRAME_stand99         	98
+#define FRAME_stand100        	99
+#define FRAME_stand101        	100
+#define FRAME_stand102        	101
+#define FRAME_stand103        	102
+#define FRAME_stand104        	103
+#define FRAME_stand105        	104
+#define FRAME_stand106        	105
+#define FRAME_stand107        	106
+#define FRAME_stand108        	107
+#define FRAME_stand109        	108
+#define FRAME_stand110        	109
+#define FRAME_stand111        	110
+#define FRAME_stand112        	111
+#define FRAME_stand113        	112
+#define FRAME_stand114        	113
+#define FRAME_stand115        	114
+#define FRAME_stand116        	115
+#define FRAME_stand117        	116
+#define FRAME_stand118        	117
+#define FRAME_stand119        	118
+#define FRAME_stand120        	119
+#define FRAME_stand121        	120
+#define FRAME_stand122        	121
+#define FRAME_stand123        	122
+#define FRAME_stand124        	123
+#define FRAME_stand125        	124
+#define FRAME_stand126        	125
+#define FRAME_stand127        	126
+#define FRAME_stand128        	127
+#define FRAME_stand129        	128
+#define FRAME_stand130        	129
+#define FRAME_stand131        	130
+#define FRAME_stand132        	131
+#define FRAME_stand133        	132
+#define FRAME_stand134        	133
+#define FRAME_stand135        	134
+#define FRAME_stand136        	135
+#define FRAME_stand137        	136
+#define FRAME_stand138        	137
+#define FRAME_stand139        	138
+#define FRAME_stand140        	139
+#define FRAME_stand141        	140
+#define FRAME_stand142        	141
+#define FRAME_stand143        	142
+#define FRAME_stand144        	143
+#define FRAME_stand145        	144
+#define FRAME_stand146        	145
+#define FRAME_stand147        	146
+#define FRAME_stand148        	147
+#define FRAME_stand149        	148
+#define FRAME_stand150        	149
+#define FRAME_stand151        	150
+#define FRAME_stand152        	151
+#define FRAME_stand153        	152
+#define FRAME_stand154        	153
+#define FRAME_stand155        	154
+#define FRAME_stand156        	155
+#define FRAME_stand157        	156
+#define FRAME_stand158        	157
+#define FRAME_stand159        	158
+#define FRAME_stand160        	159
+#define FRAME_walk27          	160
+#define FRAME_walk28          	161
+#define FRAME_walk29          	162
+#define FRAME_walk30          	163
+#define FRAME_walk31          	164
+#define FRAME_walk32          	165
+#define FRAME_walk33          	166
+#define FRAME_walk34          	167
+#define FRAME_walk35          	168
+#define FRAME_walk36          	169
+#define FRAME_walk37          	170
+#define FRAME_walk38          	171
+#define FRAME_walk39          	172
+#define FRAME_walk1           	173
+#define FRAME_walk2           	174
+#define FRAME_walk3           	175
+#define FRAME_walk4           	176
+#define FRAME_walk5           	177
+#define FRAME_walk6           	178
+#define FRAME_walk7           	179
+#define FRAME_walk8           	180
+#define FRAME_walk9           	181
+#define FRAME_walk10          	182
+#define FRAME_walk11          	183
+#define FRAME_walk12          	184
+#define FRAME_walk13          	185
+#define FRAME_walk14          	186
+#define FRAME_walk15          	187
+#define FRAME_walk16          	188
+#define FRAME_walk17          	189
+#define FRAME_walk18          	190
+#define FRAME_walk19          	191
+#define FRAME_walk20          	192
+#define FRAME_walk21          	193
+#define FRAME_walk22          	194
+#define FRAME_walk23          	195
+#define FRAME_walk24          	196
+#define FRAME_walk25          	197
+#define FRAME_walk26          	198
+#define FRAME_st_pain2        	199
+#define FRAME_st_pain3        	200
+#define FRAME_st_pain4        	201
+#define FRAME_st_pain5        	202
+#define FRAME_st_pain6        	203
+#define FRAME_st_pain7        	204
+#define FRAME_st_pain8        	205
+#define FRAME_st_pain9        	206
+#define FRAME_st_pain10       	207
+#define FRAME_st_pain11       	208
+#define FRAME_st_pain12       	209
+#define FRAME_st_death2       	210
+#define FRAME_st_death3       	211
+#define FRAME_st_death4       	212
+#define FRAME_st_death5       	213
+#define FRAME_st_death6       	214
+#define FRAME_st_death7       	215
+#define FRAME_st_death8       	216
+#define FRAME_st_death9       	217
+#define FRAME_st_death10      	218
+#define FRAME_st_death11      	219
+#define FRAME_st_death12      	220
+#define FRAME_st_death13      	221
+#define FRAME_st_death14      	222
+#define FRAME_st_death15      	223
+#define FRAME_st_death16      	224
+#define FRAME_st_death17      	225
+#define FRAME_st_death18      	226
+#define FRAME_crawl1          	227
+#define FRAME_crawl2          	228
+#define FRAME_crawl3          	229
+#define FRAME_crawl4          	230
+#define FRAME_crawl5          	231
+#define FRAME_crawl6          	232
+#define FRAME_crawl7          	233
+#define FRAME_crawl8          	234
+#define FRAME_crawl9          	235
+#define FRAME_cr_pain2        	236
+#define FRAME_cr_pain3        	237
+#define FRAME_cr_pain4        	238
+#define FRAME_cr_pain5        	239
+#define FRAME_cr_pain6        	240
+#define FRAME_cr_pain7        	241
+#define FRAME_cr_pain8        	242
+#define FRAME_cr_pain9        	243
+#define FRAME_cr_pain10       	244
+#define FRAME_cr_death10      	245
+#define FRAME_cr_death11      	246
+#define FRAME_cr_death12      	247
+#define FRAME_cr_death13      	248
+#define FRAME_cr_death14      	249
+#define FRAME_cr_death15      	250
+#define FRAME_cr_death16      	251
+#define FRAME_cross1          	252
+#define FRAME_cross2          	253
+#define FRAME_cross3          	254
+#define FRAME_cross4          	255
+#define FRAME_cross5          	256
+#define FRAME_cross6          	257
+#define FRAME_cross7          	258
+#define FRAME_cross8          	259
+#define FRAME_cross9          	260
+#define FRAME_cross10         	261
+#define FRAME_cross11         	262
+#define FRAME_cross12         	263
+#define FRAME_cross13         	264
+#define FRAME_cross14         	265
+#define FRAME_cross15         	266
+#define FRAME_cross16         	267
+#define FRAME_cross17         	268
+#define FRAME_cross18         	269
+#define FRAME_cross19         	270
+#define FRAME_cross20         	271
+#define FRAME_cross21         	272
+#define FRAME_cross22         	273
+#define FRAME_cross23         	274
+#define FRAME_cross24         	275
+#define FRAME_cross25         	276
+#define FRAME_cross26         	277
+#define FRAME_cross27         	278
+#define FRAME_cross28         	279
+#define FRAME_cross29         	280
+#define FRAME_cross30         	281
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_medic.c
@@ -1,0 +1,1789 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_medic.h"
+
+#define	MEDIC_MIN_DISTANCE	32
+#define MEDIC_MAX_HEAL_DISTANCE	400
+#define	MEDIC_TRY_TIME		10.0
+
+// FIXME -
+//
+// owner moved to monsterinfo.healer instead
+//
+// For some reason, the healed monsters are rarely ending up in the floor
+//
+// 5/15/1998 I think I fixed these, keep an eye on them
+
+qboolean visible (edict_t *self, edict_t *other);
+void M_SetEffects (edict_t *ent);
+qboolean FindTarget (edict_t *self);
+void HuntTarget (edict_t *self);
+void FoundTarget (edict_t *self);
+char *ED_NewString (char *string);
+void spawngrow_think (edict_t *self);
+void SpawnGrow_Spawn (vec3_t startpos, int size);
+void ED_CallSpawn (edict_t *ent);
+void M_FliesOff (edict_t *self);
+void M_FliesOn (edict_t *self);
+
+
+static int	sound_idle1;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_hook_launch;
+static int	sound_hook_hit;
+static int	sound_hook_heal;
+static int	sound_hook_retract;
+
+// PMM - commander sounds
+static int	commander_sound_idle1;
+static int	commander_sound_pain1;
+static int	commander_sound_pain2;
+static int	commander_sound_die;
+static int	commander_sound_sight;
+static int	commander_sound_search;
+static int	commander_sound_hook_launch;
+static int	commander_sound_hook_hit;
+static int	commander_sound_hook_heal;
+static int	commander_sound_hook_retract;
+static int	commander_sound_spawn;
+
+char * reinforcements[] = {
+	{"monster_soldier_light"},	// 0
+	{"monster_soldier"},		// 1
+	{"monster_soldier_ss"},		// 2
+	{"monster_infantry"},		// 3
+	{"monster_gunner"},			// 4
+//	{"monster_chick"},			// 4
+	{"monster_medic"},			// 5
+	{"monster_gladiator"}		// 6
+};
+
+vec3_t reinforcement_mins[] = {
+	{-16, -16, -24},
+	{-16, -16, -24},
+	{-16, -16, -24},
+	{-16, -16, -24},
+	{-16, -16, -24},
+	{-16, -16, -24},
+	{-32, -32, -24}
+};
+
+vec3_t reinforcement_maxs[] = {
+	{16, 16, 32},
+	{16, 16, 32},
+	{16, 16, 32},
+	{16, 16, 32},
+	{16, 16, 32},
+	{16, 16, 32},
+	{32, 32, 64}
+};
+
+vec3_t reinforcement_position[] = {
+	{80, 0, 0},
+	{40, 60, 0},
+	{40, -60, 0},
+	{0, 80, 0},
+	{0, -80, 0}
+};
+
+void cleanupHeal (edict_t *self, qboolean change_frame)
+{
+	// clean up target, if we have one and it's legit
+	if (self->enemy && self->enemy->inuse)
+	{
+		self->enemy->monsterinfo.healer = NULL;
+		self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+		self->enemy->takedamage = DAMAGE_YES;
+		M_SetEffects (self->enemy);
+	}
+
+	if (change_frame)
+		self->monsterinfo.nextframe = FRAME_attack52;
+}
+
+void abortHeal (edict_t *self, qboolean change_frame, qboolean gib, qboolean mark)
+{
+	int hurt;
+	static vec3_t	pain_normal = { 0, 0, 1 };
+
+	// clean up target
+	cleanupHeal (self, change_frame);
+	// gib em!
+	if ((mark) && (self->enemy) && (self->enemy->inuse))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%s - marking target as bad\n", self->classname);
+		// if the first badMedic slot is filled by a medic, skip it and use the second one
+		if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse)
+			&& (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)) )
+		{
+			self->enemy->monsterinfo.badMedic2 = self;
+		}
+		else
+		{
+			self->enemy->monsterinfo.badMedic1 = self;
+		}
+	}
+	if ((gib) && (self->enemy) && (self->enemy->inuse))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%s - gibbing bad heal target", self->classname);
+
+		if(self->enemy->gib_health)
+			hurt = - self->enemy->gib_health;
+		else
+			hurt = 500;
+
+		T_Damage (self->enemy, self, self, vec3_origin, self->enemy->s.origin,
+					pain_normal, hurt, 0, 0, MOD_UNKNOWN);
+	}
+	// clean up self
+
+	self->monsterinfo.aiflags &= ~AI_MEDIC;
+	if ((self->oldenemy) && (self->oldenemy->inuse))
+		self->enemy = self->oldenemy;
+	else
+		self->enemy = NULL;
+
+	self->monsterinfo.medicTries = 0;
+}
+
+qboolean canReach (edict_t *self, edict_t *other)
+{
+	vec3_t	spot1;
+	vec3_t	spot2;
+	trace_t	trace;
+
+	VectorCopy (self->s.origin, spot1);
+	spot1[2] += self->viewheight;
+	VectorCopy (other->s.origin, spot2);
+	spot2[2] += other->viewheight;
+	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_SHOT|MASK_WATER);
+	
+	if (trace.fraction == 1.0 || trace.ent == other)		// PGM
+		return true;
+	return false;
+}
+
+edict_t *medic_FindDeadMonster (edict_t *self)
+{
+	float	radius;
+	edict_t	*ent = NULL;
+	edict_t	*best = NULL;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		radius = MEDIC_MAX_HEAL_DISTANCE;
+	else
+		radius = 1024;
+
+	while ((ent = findradius(ent, self->s.origin, radius)) != NULL)
+	{
+		if (ent == self)
+			continue;
+		if (!(ent->svflags & SVF_MONSTER))
+			continue;
+		if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+			continue;
+		// check to make sure we haven't bailed on this guy already
+		if ((ent->monsterinfo.badMedic1 == self) || (ent->monsterinfo.badMedic2 == self))
+			continue;
+		if (ent->monsterinfo.healer)
+			// FIXME - this is correcting a bug that is somewhere else
+			// if the healer is a monster, and it's in medic mode .. continue .. otherwise
+			//   we will override the healer, if it passes all the other tests
+			if ((ent->monsterinfo.healer->inuse) && (ent->monsterinfo.healer->health > 0) &&
+				(ent->monsterinfo.healer->svflags & SVF_MONSTER) && (ent->monsterinfo.healer->monsterinfo.aiflags & AI_MEDIC))
+				continue;
+		if (ent->health > 0)
+			continue;
+		if ((ent->nextthink) && !((ent->think == M_FliesOn) || (ent->think == M_FliesOff)))
+			continue;
+		if (!visible(self, ent))
+//		if (!canReach(self, ent))
+			continue;
+		if (!strncmp(ent->classname, "player", 6))		 // stop it from trying to heal player_noise entities
+			continue;
+		// FIXME - there's got to be a better way ..
+		// make sure we don't spawn people right on top of us
+		if (realrange(self, ent) <= MEDIC_MIN_DISTANCE)
+			continue;
+		if (!best)
+		{
+			best = ent;
+			continue;
+		}
+		if (ent->max_health <= best->max_health)
+			continue;
+		best = ent;
+	}
+
+	if (best)
+		self->timestamp = level.time + MEDIC_TRY_TIME;
+
+	return best;
+}
+
+void medic_idle (edict_t *self)
+{
+	edict_t	*ent;
+
+	// PMM - commander sounds
+	if (self->mass == 400)
+		gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_VOICE, commander_sound_idle1, 1, ATTN_IDLE, 0);
+		
+
+	if (!self->oldenemy)
+	{
+		ent = medic_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->monsterinfo.healer = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+		}
+	}
+}
+
+void medic_search (edict_t *self)
+{
+	edict_t	*ent;
+
+	// PMM - commander sounds
+	if (self->mass == 400)
+		gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_VOICE, commander_sound_search, 1, ATTN_IDLE, 0);
+
+	if (!self->oldenemy)
+	{
+		ent = medic_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->monsterinfo.healer = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+		}
+	}
+}
+
+void medic_sight (edict_t *self, edict_t *)
+{
+	// PMM - commander sounds
+	if (self->mass == 400)
+		gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, commander_sound_sight, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t medic_frames_stand [] =
+{
+	ai_stand, 0, medic_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+};
+mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL};
+
+void medic_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &medic_move_stand;
+}
+
+
+mframe_t medic_frames_walk [] =
+{
+	ai_walk, 6.2,	NULL,
+	ai_walk, 18.1,  NULL,
+	ai_walk, 1,		NULL,
+	ai_walk, 9,		NULL,
+	ai_walk, 10,	NULL,
+	ai_walk, 9,		NULL,
+	ai_walk, 11,	NULL,
+	ai_walk, 11.6,  NULL,
+	ai_walk, 2,		NULL,
+	ai_walk, 9.9,	NULL,
+	ai_walk, 14,	NULL,
+	ai_walk, 9.3,	NULL
+};
+mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL};
+
+void medic_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &medic_move_walk;
+}
+
+
+mframe_t medic_frames_run [] =
+{
+	ai_run, 18,		NULL,
+	ai_run, 22.5,	NULL,
+	ai_run, 25.4,	monster_done_dodge,
+	ai_run, 23.4,	NULL,
+	ai_run, 24,		NULL,
+	ai_run, 35.6,	NULL		//pmm
+	
+};
+mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL};
+
+void medic_run (edict_t *self)
+{
+	monster_done_dodge (self);
+	if (!(self->monsterinfo.aiflags & AI_MEDIC))
+	{
+		edict_t	*ent;
+
+		ent = medic_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->monsterinfo.healer = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+			return;
+		}
+	}
+//	else if (!canReach(self, self->enemy))
+//	{
+//		abortHeal (self, 0);
+//	}
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &medic_move_stand;
+	else
+		self->monsterinfo.currentmove = &medic_move_run;
+}
+
+
+mframe_t medic_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run};
+
+mframe_t medic_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run};
+
+void medic_pain (edict_t *self, edict_t *, float, int damage)
+{
+	monster_done_dodge (self);
+
+	if ((self->health < (self->max_health / 2)))
+		if (self->mass > 400)
+			self->s.skinnum = 3;
+		else
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	// if we're healing someone, we ignore pain
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+		return;
+
+	if (self->mass > 400)
+	{
+		if (damage < 35)
+		{
+			gi.sound (self, CHAN_VOICE, commander_sound_pain1, 1, ATTN_NORM, 0);
+			return;
+		}
+
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+		gi.sound (self, CHAN_VOICE, commander_sound_pain2, 1, ATTN_NORM, 0);
+
+		if (qrandom() < (qmin(((float)damage * 0.005), 0.5)))		// no more than 50% chance of big pain
+			self->monsterinfo.currentmove = &medic_move_pain2;
+		else
+			self->monsterinfo.currentmove = &medic_move_pain1;
+	}
+	else if (qrandom() < 0.5)
+	{
+		self->monsterinfo.currentmove = &medic_move_pain1;
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &medic_move_pain2;
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	}
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+}
+
+void medic_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+	int		damage = 2;
+
+	// paranoia checking
+	if (!(self->enemy && self->enemy->inuse))
+		return;
+
+	if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
+		effect = EF_BLASTER;
+	else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	if (!strcmp(self->enemy->classname, "tesla"))
+		damage = 3;
+
+	// medic commander shoots blaster2
+	if (self->mass > 400)
+		monster_fire_blaster2 (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_2, effect);
+	else
+		monster_fire_blaster (self, start, dir, damage, 1000, MZ2_MEDIC_BLASTER_1, effect);
+}
+
+void medic_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t medic_frames_death [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead};
+
+void medic_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	// if we had a pending patient, he was already freed up in Killed
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	//	PMM
+	if (self->mass == 400)
+		gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, commander_sound_die, 1, ATTN_NORM, 0);
+	//
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &medic_move_death;
+}
+
+mframe_t medic_frames_duck [] =
+{
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	monster_duck_down,
+	ai_move, -1,	monster_duck_hold,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,		// PMM - duck up used to be here
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	monster_duck_up,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL
+};
+mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run};
+
+// PMM -- moved dodge code to after attack code so I can reference attack frames
+
+mframe_t medic_frames_attackHyperBlaster [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster
+};
+mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run};
+
+
+void medic_continue (edict_t *self)
+{
+	if (visible (self, self->enemy) )
+		if (qrandom() <= 0.95)
+			self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
+}
+
+
+mframe_t medic_frames_attackBlaster [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 2,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,	
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_continue	// Change to medic_continue... Else, go to frame 32
+};
+mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run};
+
+
+void medic_hook_launch (edict_t *self)
+{
+	// PMM - commander sounds
+	if (self->mass == 400)
+		gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, commander_sound_hook_launch, 1, ATTN_NORM, 0);
+}
+
+static vec3_t	medic_cable_offsets[] =
+{
+	45.0,  -9.2, 15.5,
+	48.4,  -9.7, 15.2,
+	47.8,  -9.8, 15.8,
+	47.3,  -9.3, 14.3,
+	45.4, -10.1, 13.1,
+	41.9, -12.7, 12.0,
+	37.8, -15.8, 11.2,
+	34.3, -18.4, 10.7,
+	32.7, -19.7, 10.4,
+	32.7, -19.7, 10.4
+};
+
+void medic_cable_attack (edict_t *self)
+{
+	vec3_t	offset, start, end, f, r;
+	trace_t	tr;
+	vec3_t	dir;
+//	vec3_t  angles;
+	float	distance;
+
+	if ((!self->enemy) || (!self->enemy->inuse) || (self->enemy->s.effects & EF_GIB))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander - aborting heal due to target's disappearance\n");
+		abortHeal (self, true, false, false);
+		return;
+	}
+
+	// see if our enemy has changed to a client, or our target has more than 0 health,
+	// abort it .. we got switched to someone else due to damage
+	if ((self->enemy->client) || (self->enemy->health > 0))
+	{
+		abortHeal (self, true, false, false);
+		return;
+	}
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	// check for max distance
+	// not needed, done in checkattack
+	// check for min distance
+	VectorSubtract (start, self->enemy->s.origin, dir);
+	distance = VectorLength(dir);
+	if (distance < MEDIC_MIN_DISTANCE)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander - aborting heal due to proximity to target ");
+		abortHeal (self, true, true, false );
+		return;
+	}
+//	if ((g_showlogic)&&(g_showlogic->value))
+//		gi.dprintf ("distance to target is %f\n", distance);
+//	if (distance > 300)
+//		return;
+
+	// check for min/max pitch
+	// PMM -- took out since it doesn't look bad when it fails
+//	vectoangles (dir, angles);
+//	if (angles[0] < -180)
+//		angles[0] += 360;
+//	if (fabs(angles[0]) > 45)
+//	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander - aborting heal due to bad angle\n");
+//		abortHeal(self);
+//		return;
+//	}
+
+	tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SOLID);
+	if (tr.fraction != 1.0 && tr.ent != self->enemy)
+	{
+		if (tr.ent == WORLD)
+		{
+			// give up on second try
+			if (self->monsterinfo.medicTries > 1)
+			{
+				abortHeal (self, true, false, true);
+				return;
+			}
+			self->monsterinfo.medicTries++;
+			cleanupHeal (self, 1);
+			return;
+		}
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander - aborting heal due to beamus interruptus\n");
+		abortHeal (self, true, false, false);
+		return;
+	}
+
+	if (self->s.frame == FRAME_attack43)
+	{
+		// PMM - commander sounds
+		if (self->mass == 400)
+			gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self->enemy, CHAN_AUTO, commander_sound_hook_hit, 1, ATTN_NORM, 0);
+
+		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+		self->enemy->takedamage = DAMAGE_NO;
+		M_SetEffects (self->enemy);
+	}
+	else if (self->s.frame == FRAME_attack50)
+	{
+		vec3_t	maxs;
+		self->enemy->spawnflags = 0;
+		self->enemy->monsterinfo.aiflags = 0;
+		self->enemy->target = NULL;
+		self->enemy->targetname = NULL;
+		self->enemy->combattarget = NULL;
+		self->enemy->deathtarget = NULL;
+		self->enemy->monsterinfo.healer = self;
+
+		VectorCopy (self->enemy->maxs, maxs);
+		maxs[2] += 48;   // compensate for change when they die
+
+		tr = gi.trace (self->enemy->s.origin, self->enemy->mins, maxs, self->enemy->s.origin, self->enemy, MASK_MONSTERSOLID);
+		if (tr.startsolid || tr.allsolid)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("Spawn point obstructed, aborting heal!\n");
+			abortHeal (self, true, true, false);
+			return;
+		} 
+		else if (tr.ent != WORLD)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf("heal in entity %s\n", tr.ent->classname);
+			abortHeal (self, true, true, false);
+			return;
+		}
+/*		else if (tr.ent == WORLD)
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf ("heal in world, aborting!\n");
+			abortHeal (self, 1);
+			return;
+		}
+*/		else
+		{
+			self->enemy->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+			ED_CallSpawn (self->enemy);
+
+			if (self->enemy->think)
+			{
+				self->enemy->nextthink = level.time;
+				self->enemy->think (self->enemy);
+			}
+	//		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+			self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+			self->enemy->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT;
+			// turn off flies
+			self->enemy->s.effects &= ~EF_FLIES;
+			self->enemy->monsterinfo.healer = NULL;
+
+			if ((self->oldenemy) && (self->oldenemy->inuse) && (self->oldenemy->health > 0))
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("setting heal target's enemy to %s\n", self->oldenemy->classname);
+				self->enemy->enemy = self->oldenemy;
+//				HuntTarget (self->enemy);
+				FoundTarget (self->enemy);
+			}
+			else
+			{
+//				if (g_showlogic && g_showlogic->value)
+//					gi.dprintf ("no valid enemy to set!\n");
+				self->enemy->enemy = NULL;
+				if (!FindTarget (self->enemy))
+				{
+					// no valid enemy, so stop acting
+					self->enemy->monsterinfo.pausetime = level.time + 100000000;
+					self->enemy->monsterinfo.stand (self->enemy);
+				}
+				self->enemy = NULL;
+				self->oldenemy = NULL;
+				if (!FindTarget (self))
+				{
+					// no valid enemy, so stop acting
+					self->monsterinfo.pausetime = level.time + 100000000;
+					self->monsterinfo.stand (self);
+					return;
+				}
+			}
+		}
+	}
+	else
+	{
+		if (self->s.frame == FRAME_attack44)
+			// PMM - medic commander sounds
+			if (self->mass == 400)
+				gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
+			else
+				gi.sound (self, CHAN_WEAPON, commander_sound_hook_heal, 1, ATTN_NORM, 0);
+	}
+
+	// adjust start for beam origin being in middle of a segment
+	VectorMA (start, 8, f, start);
+
+	// adjust end z for end spot since the monster is currently dead
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+void medic_hook_retract (edict_t *self)
+{
+	if (self->mass == 400)
+		gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
+
+	self->monsterinfo.aiflags &= ~AI_MEDIC;
+	if ((self->oldenemy) && (self->oldenemy->inuse))
+		self->enemy = self->oldenemy;
+	else
+	{
+		self->enemy = NULL;
+		self->oldenemy = NULL;
+		if (!FindTarget (self))
+		{
+			// no valid enemy, so stop acting
+			self->monsterinfo.pausetime = level.time + 100000000;
+			self->monsterinfo.stand (self);
+			return;
+		}
+	}
+}
+
+mframe_t medic_frames_attackCable [] =
+{
+// ROGUE - negated 36-40 so he scoots back from his target a little
+// ROGUE - switched 33-36 to ai_charge
+// ROGUE - changed frame 52 to 0 to compensate for changes in 36-40
+	ai_charge, 2,		NULL,					//33
+	ai_charge, 3,		NULL,
+	ai_charge, 5,		NULL,
+	ai_charge, -4.4,	NULL,					//36
+	ai_charge, -4.7,	NULL,					//37
+	ai_charge, -5,	NULL,
+	ai_charge, -6,	NULL,
+	ai_charge, -4,	NULL,					//40
+	ai_charge, 0,	NULL,
+	ai_move, 0,		medic_hook_launch,		//42
+	ai_move, 0,		medic_cable_attack,		//43
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,		//51
+	ai_move, 0,		medic_hook_retract,		//52
+	ai_move, -1.5,	NULL,
+	ai_move, -1.2,	NULL,
+	ai_move, -3,	NULL,
+	ai_move, -2,	NULL,
+	ai_move, 0.3,	NULL,
+	ai_move, 0.7,	NULL,
+	ai_move, 1.2,	NULL,
+	ai_move, 1.3,	NULL					//60
+};
+mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run};
+
+
+void medic_start_spawn (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
+	self->monsterinfo.nextframe = FRAME_attack48;
+}
+
+void medic_determine_spawn (edict_t *self)
+{
+	vec3_t	f, r, offset, startpoint, spawnpoint;
+	float	lucky;
+	int		summonStr;
+	int		count;
+	int		inc;
+	int		num_summoned; // should be 1, 3, or 5
+	int		num_success = 0;
+
+	lucky = qrandom();
+	summonStr = skill->value;
+
+	// bell curve - 0 = 67%, 1 = 93%, 2 = 99% -- too steep
+	// this ends up with
+	// -3 = 5%
+	// -2 = 10%
+	// -1 = 15%
+	// 0  = 40%
+	// +1 = 15%
+	// +2 = 10%
+	// +3 = 5%
+	if (lucky < 0.05)
+		summonStr -= 3;
+	else if (lucky < 0.15)
+		summonStr -= 2;
+	else if (lucky < 0.3)
+		summonStr -= 1;
+	else if (lucky > 0.95)
+		summonStr += 3;
+	else if (lucky > 0.85)
+		summonStr += 2;
+	else if (lucky > 0.7)
+		summonStr += 1;
+
+	if (summonStr < 0)
+		summonStr = 0;
+
+	//FIXME - need to remember this, might as well use this int that isn't used for monsters
+	self->plat2flags = summonStr;
+	AngleVectors (self->s.angles, f, r, NULL);
+
+// this yields either 1, 3, or 5
+	if (summonStr)
+		num_summoned = (summonStr - 1) + (summonStr % 2);
+	else
+		num_summoned = 1;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
+
+	for (count = 0; count < num_summoned; count++)
+	{
+		inc = count + (count%2); // 0, 2, 2, 4, 4
+		VectorCopy (reinforcement_position[count], offset);
+
+		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+		// a little off the ground
+		startpoint[2] += 10;
+
+		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
+		{
+			if (CheckGroundSpawnPoint(spawnpoint, 
+				reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], 
+				256, -1))
+			{
+				num_success++;
+				// we found a spot, we're done here
+				count = num_summoned;
+			}
+//			else if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("medic_commander: CheckGroundSpawnPoint says bad stuff down there!\n");
+		}
+	}
+
+	if (num_success == 0)
+	{
+		for (count = 0; count < num_summoned; count++)
+		{
+			inc = count + (count%2); // 0, 2, 2, 4, 4
+			VectorCopy (reinforcement_position[count], offset);
+			
+			// check behind
+			offset[0] *= -1.0;
+			offset[1] *= -1.0;
+			G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+			// a little off the ground
+			startpoint[2] += 10;
+
+			if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
+			{
+				if (CheckGroundSpawnPoint(spawnpoint, 
+					reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], 
+					256, -1))
+				{
+					num_success++;
+					// we found a spot, we're done here
+					count = num_summoned;
+				}
+			}
+		}
+
+		if (num_success)
+		{
+			self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+			self->ideal_yaw = anglemod(self->s.angles[YAW]) + 180;
+			if (self->ideal_yaw > 360.0)
+				self->ideal_yaw -= 360.0;
+//			self->plat2flags *= -1;
+		}
+	}
+
+	if (num_success == 0)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander: failed to find any spawn points, aborting!\n");
+		self->monsterinfo.nextframe = FRAME_attack53;
+	}
+}
+
+void medic_spawngrows (edict_t *self)
+{
+	vec3_t	f, r, offset, startpoint, spawnpoint;
+	int		summonStr;
+	int		count;
+	int		inc;
+	int		num_summoned; // should be 1, 3, or 5
+	int		num_success = 0;
+	float	current_yaw;
+
+	// if we've been directed to turn around
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		current_yaw = anglemod(self->s.angles[YAW]);
+		if (fabs(current_yaw - self->ideal_yaw) > 0.1)
+		{
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+			return;
+		}
+
+		// done turning around
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+	}
+
+//	if (self->plat2flags < 0)
+//	{
+//		summonStr = -1.0 * self->plat2flags;
+//		behind = true;
+//	}
+//	else
+		summonStr = self->plat2flags;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+
+//	num_summoned = ((((summonStr-1)/2)+1)*2)-1;  // this yields either 1, 3, or 5
+	if (summonStr)
+		num_summoned = (summonStr - 1) + (summonStr % 2);
+	else
+		num_summoned = 1;
+
+	for (count = 0; count < num_summoned; count++)
+	{
+		inc = count + (count%2); // 0, 2, 2, 4, 4
+		VectorCopy (reinforcement_position[count], offset);
+
+		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+		// a little off the ground
+		startpoint[2] += 10;
+
+		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
+		{
+			if (CheckGroundSpawnPoint(spawnpoint, 
+				reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], 
+				256, -1))
+			{
+				num_success++;
+				if ((summonStr-inc) > 3)
+					SpawnGrow_Spawn (spawnpoint, 1);		// big monster
+				else
+					SpawnGrow_Spawn (spawnpoint, 0);		// normal size
+			}
+		}
+	}
+
+	if (num_success == 0)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander: spawngrows bad, aborting!\n");
+		self->monsterinfo.nextframe = FRAME_attack53;
+	}
+}
+
+void medic_finish_spawn (edict_t *self)
+{
+	edict_t *ent;
+	vec3_t	f, r, offset, startpoint, spawnpoint;
+	int		summonStr;
+	int		count;
+	int		inc;
+	int		num_summoned; // should be 1, 3, or 5
+	edict_t	*designated_enemy;
+
+//	trace_t		tr;
+//	vec3_t mins, maxs;
+
+	// this is one bigger than the soldier's real mins .. just for paranoia's sake
+//	VectorSet (mins, -17, -17, -25);
+//	VectorSet (maxs, 17, 17, 33);
+
+	//FIXME - better place to store this info?
+	if (self->plat2flags < 0)
+		self->plat2flags *= -1;
+	summonStr = self->plat2flags;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+
+//	num_summoned = ((((summonStr-1)/2)+1)*2)-1;  // this yields either 1, 3, or 5
+	if (summonStr)
+		num_summoned = (summonStr - 1) + (summonStr % 2);
+	else
+		num_summoned = 1;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("medic_commander: summonStr = %d num = %d\n", summonStr, num_summoned);
+
+	for (count = 0; count < num_summoned; count++)
+	{
+		inc = count + (count%2); // 0, 2, 2, 4, 4
+		VectorCopy (reinforcement_position[count], offset);
+
+		G_ProjectSource (self->s.origin, offset, f, r, startpoint);
+
+		// a little off the ground
+		startpoint[2] += 10;
+
+		ent = NULL;
+		if (FindSpawnPoint (startpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc], spawnpoint, 32))
+		{
+			if (CheckSpawnPoint (spawnpoint, reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc]))
+				ent = CreateGroundMonster (spawnpoint, self->s.angles,
+					reinforcement_mins[summonStr-inc], reinforcement_maxs[summonStr-inc],
+					reinforcements[summonStr-inc], 256);
+//			else if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("CheckSpawnPoint failed volume check!\n");
+		}
+		else
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("FindSpawnPoint failed to find a point!\n");
+		}
+
+		if (!ent)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("Spawn point obstructed for %s, aborting!\n", reinforcements[summonStr-inc]);
+			continue;
+		}
+
+//		gi.sound (self, CHAN_WEAPON, commander_sound_spawn, 1, ATTN_NORM, 0);
+
+		if (ent->think)
+		{
+			ent->nextthink = level.time;
+			ent->think (ent);
+		}
+
+		ent->monsterinfo.aiflags |= AI_IGNORE_SHOTS|AI_DO_NOT_COUNT|AI_SPAWNED_MEDIC_C;
+		ent->monsterinfo.commander = self;
+		self->monsterinfo.monster_slots--;
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("medic_commander: %d slots remaining\n", self->monsterinfo.monster_slots);
+
+		if (self->monsterinfo.aiflags & AI_MEDIC)
+			designated_enemy = self->oldenemy;
+		else
+			designated_enemy = self->enemy;
+
+		if (coop && coop->value)
+		{
+			designated_enemy = PickCoopTarget(ent);
+			if (designated_enemy)
+			{
+				// try to avoid using my enemy
+				if (designated_enemy == self->enemy)
+				{
+					designated_enemy = PickCoopTarget(ent);
+					if (designated_enemy)
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//						{
+//							gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
+//							if (designated_enemy->client)
+//								gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
+//							else
+//								gi.dprintf ("NOT A CLIENT\n");
+//						}
+					}
+					else
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("pick coop failed, using my current enemy\n");
+						designated_enemy = self->enemy;
+					}
+				}
+			}
+			else
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf ("pick coop failed, using my current enemy\n");
+				designated_enemy = self->enemy;
+			}
+		}
+
+		if ((designated_enemy) && (designated_enemy->inuse) && (designated_enemy->health > 0))
+		{
+			// fixme
+//			if ((g_showlogic) && (g_showlogic -> value))
+//				gi.dprintf  ("setting enemy to %s\n", designated_enemy->classname);
+			ent->enemy = designated_enemy;
+			FoundTarget (ent);
+		}
+		else
+		{
+			ent->enemy = NULL;
+			ent->monsterinfo.stand (ent);
+		}
+//		ent->s.event = EV_PLAYER_TELEPORT;
+	}
+}
+
+mframe_t medic_frames_callReinforcements [] =
+{
+	// ROGUE - 33-36 now ai_charge
+	ai_charge, 2,		NULL,					//33
+	ai_charge, 3,		NULL,
+	ai_charge, 5,		NULL,
+	ai_charge, 4.4,	NULL,					//36
+	ai_charge, 4.7,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 4,	NULL,					//40
+	ai_charge, 0,	NULL,
+	ai_move, 0,		medic_start_spawn,		//42
+	ai_move, 0,		NULL,		//43 -- 43 through 47 are skipped
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 0,		medic_determine_spawn,		//48
+	ai_charge, 0,		medic_spawngrows,			//49
+	ai_move, 0,		NULL,		//50
+	ai_move, 0,		NULL,		//51
+	ai_move, -15,	medic_finish_spawn,		//52
+	ai_move, -1.5,	NULL,
+	ai_move, -1.2,	NULL,
+	ai_move, -3,	NULL,
+	ai_move, -2,	NULL,
+	ai_move, 0.3,	NULL,
+	ai_move, 0.7,	NULL,
+	ai_move, 1.2,	NULL,
+	ai_move, 1.3,	NULL					//60
+};
+mmove_t medic_move_callReinforcements = {FRAME_attack33, FRAME_attack60, medic_frames_callReinforcements, medic_run};
+
+void medic_attack(edict_t *self)
+{
+	int		enemy_range;
+	float	r;
+
+	monster_done_dodge (self);
+
+	enemy_range = range(self, self->enemy);
+
+	// signal from checkattack to spawn
+	if (self->monsterinfo.aiflags & AI_BLOCKED)
+	{
+		self->monsterinfo.currentmove = &medic_move_callReinforcements;
+		self->monsterinfo.aiflags &= ~AI_BLOCKED;
+	}
+
+	r = qrandom();
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if ((self->mass > 400) && (r > 0.8) && (self->monsterinfo.monster_slots > 2))
+			self->monsterinfo.currentmove = &medic_move_callReinforcements;
+		else	
+			self->monsterinfo.currentmove = &medic_move_attackCable;
+	}
+	else
+	{
+		if (self->monsterinfo.attack_state == AS_BLIND)
+		{
+			self->monsterinfo.currentmove = &medic_move_callReinforcements;
+			return;
+		}
+		if ((self->mass > 400) && (r > 0.2) && (enemy_range != RANGE_MELEE) && (self->monsterinfo.monster_slots > 2))
+			self->monsterinfo.currentmove = &medic_move_callReinforcements;
+		else
+			self->monsterinfo.currentmove = &medic_move_attackBlaster;
+	}
+}
+
+qboolean medic_checkattack (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		// if our target went away
+		if ((!self->enemy) || (!self->enemy->inuse))
+		{
+//			if (g_showlogic && g_showlogic->value)
+//				gi.dprintf ("aborting heal target due to gib\n");
+			abortHeal (self, true, false, false);
+			return false;
+		}
+
+		// if we ran out of time, give up
+		if (self->timestamp < level.time)
+		{
+//			if (g_showlogic && g_showlogic->value)
+//				gi.dprintf ("aborting heal target (%s) due to time\n", self->enemy->classname);
+			abortHeal (self, true, false, true);
+			self->timestamp = 0;
+			return false;
+		}
+	
+		if (realrange(self, self->enemy) < MEDIC_MAX_HEAL_DISTANCE+10)
+		{
+			medic_attack(self);
+			return true;
+		}
+		else
+		{
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+			return false;
+		}
+	}
+
+	if (self->enemy->client && !visible (self, self->enemy) && (self->monsterinfo.monster_slots > 2))
+	{
+		self->monsterinfo.attack_state = AS_BLIND;
+		return true;
+	}
+
+	// give a LARGE bias to spawning things when we have room
+	// use AI_BLOCKED as a signal to attack to spawn
+	if ((qrandom() < 0.8) && (self->monsterinfo.monster_slots > 5) && (realrange(self, self->enemy) > 150))
+	{
+		self->monsterinfo.aiflags |= AI_BLOCKED;
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+	// ROGUE
+	// since his idle animation looks kinda bad in combat, if we're not in easy mode, always attack
+	// when he's on a combat point
+	if (skill->value > 0)
+		if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		{
+			self->monsterinfo.attack_state = AS_MISSILE;
+			return true;
+		}
+
+	return M_CheckAttack (self);
+}
+/*
+void medic_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+
+	if (random() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &medic_move_duck;
+
+//===========
+//PMM - rogue rewrite of dodge code.
+	float	r;
+	float	height;
+	int		shooting = 0;
+
+	// this needs to be before the AI_MEDIC check, because
+	//   a) if AI_MEDIC is set, we should have an enemy anyway and
+	//   b) FoundTarget calls medic_run, which can set AI_MEDIC
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+//	don't dodge if you're healing
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+		return;
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	r = qrandom();
+	if (r > (0.25*((skill->value)+1)))
+		return;
+
+	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
+		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
+		(self->monsterinfo.currentmove == &medic_move_attackBlaster))
+	{
+		shooting = 1;
+	}
+	if (self->monsterinfo.aiflags & AI_DODGING)
+	{
+		height = self->absmax[2];
+	}
+	else
+	{
+		height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+	}
+
+	// check to see if it makes sense to duck
+	if (tr->endpos[2] <= height)
+	{
+		vec3_t right, diff;
+		if (shooting)
+		{
+			self->monsterinfo.attack_state = AS_SLIDING;
+			return;
+		}
+		AngleVectors (self->s.angles, NULL, right, NULL);
+		VectorSubtract (tr->endpos, self->s.origin, diff);
+		if (DotProduct (right, diff) < 0)
+		{
+			self->monsterinfo.lefty = 1;
+		}
+		// if it doesn't sense to duck, try to strafe away
+		monster_done_dodge (self);
+		self->monsterinfo.currentmove = &medic_move_run;
+		self->monsterinfo.attack_state = AS_SLIDING;
+		return;
+	}
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &medic_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.aiflags |= AI_DODGING;
+		return;
+	}
+
+	if (!shooting)
+	{
+		self->monsterinfo.currentmove = &medic_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+		self->monsterinfo.aiflags |= AI_DODGING;
+	}
+	return;
+
+//PMM
+//===========
+
+}
+*/
+void MedicCommanderCache (void)
+{
+	edict_t	*newEnt;
+	int i;
+
+	//FIXME - better way to do this?  this is quick and dirty
+	for (i=0; i < 7; i++)
+	{
+		newEnt = G_Spawn();
+
+		VectorCopy(vec3_origin, newEnt->s.origin);
+		VectorCopy(vec3_origin, newEnt->s.angles);
+		newEnt->classname = ED_NewString (reinforcements[i]);
+		
+		newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+		ED_CallSpawn(newEnt);
+		// FIXME - could copy mins/maxs into reinforcements from here
+		G_FreeEdict (newEnt);
+	}
+
+	//modelidx = gi.modelindex("models/items/spawngro/tris.md2");
+	//modelidx = gi.modelindex("models/items/spawngro2/tris.md2");
+}
+
+void medic_duck (edict_t *self, float eta)
+{
+//	don't dodge if you're healing
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+		return;
+
+	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
+		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
+		(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
+		(self->monsterinfo.currentmove == &medic_move_callReinforcements))
+	{
+		// he ignores skill
+		self->monsterinfo.aiflags &= ~AI_DUCKED;
+		return;
+	}
+
+	if (skill->value == 0)
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	else
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+
+	// has to be done immediately otherwise he can get stuck
+	monster_duck_down(self);
+
+	self->monsterinfo.nextframe = FRAME_duck1;
+	self->monsterinfo.currentmove = &medic_move_duck;
+	return;
+}
+
+void medic_sidestep (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &medic_move_attackHyperBlaster) ||
+		(self->monsterinfo.currentmove == &medic_move_attackCable) ||
+		(self->monsterinfo.currentmove == &medic_move_attackBlaster) ||
+		(self->monsterinfo.currentmove == &medic_move_callReinforcements))
+	{
+		// if we're shooting, and not on easy, don't dodge
+		if (skill->value)
+		{
+			self->monsterinfo.aiflags &= ~AI_DODGING;
+			return;
+		}
+	}
+
+	if (self->monsterinfo.currentmove != &medic_move_run)
+		self->monsterinfo.currentmove = &medic_move_run;
+}
+
+//===========
+//PGM
+qboolean medic_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+/*QUAKED monster_medic_commander (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_medic (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+//PMM
+	if (strcmp(self->classname, "monster_medic_commander") == 0)
+	{
+		self->health = 600;			//	fixme
+		self->gib_health = -130;
+		self->mass = 600;
+		self->yaw_speed = 40; // default is 20
+		MedicCommanderCache();
+//		self->s.skinnum = 2;
+	}
+	else
+	{
+//PMM
+		self->health = 300;
+		self->gib_health = -130;
+		self->mass = 400;
+//		self->s.skinnum = 0;
+	}
+
+	self->pain = medic_pain;
+	self->die = medic_die;
+
+	self->monsterinfo.stand = medic_stand;
+	self->monsterinfo.walk = medic_walk;
+	self->monsterinfo.run = medic_run;
+	// pmm
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.duck = medic_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+	self->monsterinfo.sidestep = medic_sidestep;
+//	self->monsterinfo.dodge = medic_dodge;
+	// pmm
+	self->monsterinfo.attack = medic_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = medic_sight;
+	self->monsterinfo.idle = medic_idle;
+	self->monsterinfo.search = medic_search;
+	self->monsterinfo.checkattack = medic_checkattack;
+	self->monsterinfo.blocked = medic_blocked;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &medic_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+
+	//PMM
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+
+	if (self->mass > 400)
+	{
+		self->s.skinnum = 2;
+		if (skill->value == 0)
+			self->monsterinfo.monster_slots = 3;
+		else if (skill->value == 1)
+			self->monsterinfo.monster_slots = 4;
+		else if (skill->value == 2)
+			self->monsterinfo.monster_slots = 6;
+		else if (skill->value == 3)
+			self->monsterinfo.monster_slots = 6;
+		// commander sounds
+		commander_sound_idle1 = gi.soundindex ("medic_commander/medidle.wav");
+		commander_sound_pain1 = gi.soundindex ("medic_commander/medpain1.wav");
+		commander_sound_pain2 = gi.soundindex ("medic_commander/medpain2.wav");
+		commander_sound_die = gi.soundindex ("medic_commander/meddeth.wav");
+		commander_sound_sight = gi.soundindex ("medic_commander/medsght.wav");
+		commander_sound_search = gi.soundindex ("medic_commander/medsrch.wav");
+		commander_sound_hook_launch = gi.soundindex ("medic_commander/medatck2c.wav");
+		commander_sound_hook_hit = gi.soundindex ("medic_commander/medatck3a.wav");
+		commander_sound_hook_heal = gi.soundindex ("medic_commander/medatck4a.wav");
+		commander_sound_hook_retract = gi.soundindex ("medic_commander/medatck5a.wav");
+		commander_sound_spawn = gi.soundindex ("medic_commander/monsterspawn1.wav");
+		gi.soundindex ("tank/tnkatck3.wav");
+	}
+	else
+	{
+		sound_idle1 = gi.soundindex ("medic/idle.wav");
+		sound_pain1 = gi.soundindex ("medic/medpain1.wav");
+		sound_pain2 = gi.soundindex ("medic/medpain2.wav");
+		sound_die = gi.soundindex ("medic/meddeth1.wav");
+		sound_sight = gi.soundindex ("medic/medsght1.wav");
+		sound_search = gi.soundindex ("medic/medsrch1.wav");
+		sound_hook_launch = gi.soundindex ("medic/medatck2.wav");
+		sound_hook_hit = gi.soundindex ("medic/medatck3.wav");
+		sound_hook_heal = gi.soundindex ("medic/medatck4.wav");
+		sound_hook_retract = gi.soundindex ("medic/medatck5.wav");
+		gi.soundindex ("medic/medatck1.wav");
+
+		self->s.skinnum = 0;
+	}
+	//pmm
+}
--- /dev/null
+++ b/rogue/m_medic.h
@@ -1,0 +1,243 @@
+// G:\quake2\baseq2\models/monsters/medic
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_walk1           	0
+#define FRAME_walk2           	1
+#define FRAME_walk3           	2
+#define FRAME_walk4           	3
+#define FRAME_walk5           	4
+#define FRAME_walk6           	5
+#define FRAME_walk7           	6
+#define FRAME_walk8           	7
+#define FRAME_walk9           	8
+#define FRAME_walk10          	9
+#define FRAME_walk11          	10
+#define FRAME_walk12          	11
+#define FRAME_wait1           	12
+#define FRAME_wait2           	13
+#define FRAME_wait3           	14
+#define FRAME_wait4           	15
+#define FRAME_wait5           	16
+#define FRAME_wait6           	17
+#define FRAME_wait7           	18
+#define FRAME_wait8           	19
+#define FRAME_wait9           	20
+#define FRAME_wait10          	21
+#define FRAME_wait11          	22
+#define FRAME_wait12          	23
+#define FRAME_wait13          	24
+#define FRAME_wait14          	25
+#define FRAME_wait15          	26
+#define FRAME_wait16          	27
+#define FRAME_wait17          	28
+#define FRAME_wait18          	29
+#define FRAME_wait19          	30
+#define FRAME_wait20          	31
+#define FRAME_wait21          	32
+#define FRAME_wait22          	33
+#define FRAME_wait23          	34
+#define FRAME_wait24          	35
+#define FRAME_wait25          	36
+#define FRAME_wait26          	37
+#define FRAME_wait27          	38
+#define FRAME_wait28          	39
+#define FRAME_wait29          	40
+#define FRAME_wait30          	41
+#define FRAME_wait31          	42
+#define FRAME_wait32          	43
+#define FRAME_wait33          	44
+#define FRAME_wait34          	45
+#define FRAME_wait35          	46
+#define FRAME_wait36          	47
+#define FRAME_wait37          	48
+#define FRAME_wait38          	49
+#define FRAME_wait39          	50
+#define FRAME_wait40          	51
+#define FRAME_wait41          	52
+#define FRAME_wait42          	53
+#define FRAME_wait43          	54
+#define FRAME_wait44          	55
+#define FRAME_wait45          	56
+#define FRAME_wait46          	57
+#define FRAME_wait47          	58
+#define FRAME_wait48          	59
+#define FRAME_wait49          	60
+#define FRAME_wait50          	61
+#define FRAME_wait51          	62
+#define FRAME_wait52          	63
+#define FRAME_wait53          	64
+#define FRAME_wait54          	65
+#define FRAME_wait55          	66
+#define FRAME_wait56          	67
+#define FRAME_wait57          	68
+#define FRAME_wait58          	69
+#define FRAME_wait59          	70
+#define FRAME_wait60          	71
+#define FRAME_wait61          	72
+#define FRAME_wait62          	73
+#define FRAME_wait63          	74
+#define FRAME_wait64          	75
+#define FRAME_wait65          	76
+#define FRAME_wait66          	77
+#define FRAME_wait67          	78
+#define FRAME_wait68          	79
+#define FRAME_wait69          	80
+#define FRAME_wait70          	81
+#define FRAME_wait71          	82
+#define FRAME_wait72          	83
+#define FRAME_wait73          	84
+#define FRAME_wait74          	85
+#define FRAME_wait75          	86
+#define FRAME_wait76          	87
+#define FRAME_wait77          	88
+#define FRAME_wait78          	89
+#define FRAME_wait79          	90
+#define FRAME_wait80          	91
+#define FRAME_wait81          	92
+#define FRAME_wait82          	93
+#define FRAME_wait83          	94
+#define FRAME_wait84          	95
+#define FRAME_wait85          	96
+#define FRAME_wait86          	97
+#define FRAME_wait87          	98
+#define FRAME_wait88          	99
+#define FRAME_wait89          	100
+#define FRAME_wait90          	101
+#define FRAME_run1            	102
+#define FRAME_run2            	103
+#define FRAME_run3            	104
+#define FRAME_run4            	105
+#define FRAME_run5            	106
+#define FRAME_run6            	107
+#define FRAME_paina1          	108
+#define FRAME_paina2          	109
+#define FRAME_paina3          	110
+#define FRAME_paina4          	111
+#define FRAME_paina5          	112
+#define FRAME_paina6          	113
+#define FRAME_paina7          	114
+#define FRAME_paina8          	115
+#define FRAME_painb1          	116
+#define FRAME_painb2          	117
+#define FRAME_painb3          	118
+#define FRAME_painb4          	119
+#define FRAME_painb5          	120
+#define FRAME_painb6          	121
+#define FRAME_painb7          	122
+#define FRAME_painb8          	123
+#define FRAME_painb9          	124
+#define FRAME_painb10         	125
+#define FRAME_painb11         	126
+#define FRAME_painb12         	127
+#define FRAME_painb13         	128
+#define FRAME_painb14         	129
+#define FRAME_painb15         	130
+#define FRAME_duck1           	131
+#define FRAME_duck2           	132
+#define FRAME_duck3           	133
+#define FRAME_duck4           	134
+#define FRAME_duck5           	135
+#define FRAME_duck6           	136
+#define FRAME_duck7           	137
+#define FRAME_duck8           	138
+#define FRAME_duck9           	139
+#define FRAME_duck10          	140
+#define FRAME_duck11          	141
+#define FRAME_duck12          	142
+#define FRAME_duck13          	143
+#define FRAME_duck14          	144
+#define FRAME_duck15          	145
+#define FRAME_duck16          	146
+#define FRAME_death1          	147
+#define FRAME_death2          	148
+#define FRAME_death3          	149
+#define FRAME_death4          	150
+#define FRAME_death5          	151
+#define FRAME_death6          	152
+#define FRAME_death7          	153
+#define FRAME_death8          	154
+#define FRAME_death9          	155
+#define FRAME_death10         	156
+#define FRAME_death11         	157
+#define FRAME_death12         	158
+#define FRAME_death13         	159
+#define FRAME_death14         	160
+#define FRAME_death15         	161
+#define FRAME_death16         	162
+#define FRAME_death17         	163
+#define FRAME_death18         	164
+#define FRAME_death19         	165
+#define FRAME_death20         	166
+#define FRAME_death21         	167
+#define FRAME_death22         	168
+#define FRAME_death23         	169
+#define FRAME_death24         	170
+#define FRAME_death25         	171
+#define FRAME_death26         	172
+#define FRAME_death27         	173
+#define FRAME_death28         	174
+#define FRAME_death29         	175
+#define FRAME_death30         	176
+#define FRAME_attack1         	177
+#define FRAME_attack2         	178
+#define FRAME_attack3         	179
+#define FRAME_attack4         	180
+#define FRAME_attack5         	181
+#define FRAME_attack6         	182
+#define FRAME_attack7         	183
+#define FRAME_attack8         	184
+#define FRAME_attack9         	185
+#define FRAME_attack10        	186
+#define FRAME_attack11        	187
+#define FRAME_attack12        	188
+#define FRAME_attack13        	189
+#define FRAME_attack14        	190
+#define FRAME_attack15        	191
+#define FRAME_attack16        	192
+#define FRAME_attack17        	193
+#define FRAME_attack18        	194
+#define FRAME_attack19        	195
+#define FRAME_attack20        	196
+#define FRAME_attack21        	197
+#define FRAME_attack22        	198
+#define FRAME_attack23        	199
+#define FRAME_attack24        	200
+#define FRAME_attack25        	201
+#define FRAME_attack26        	202
+#define FRAME_attack27        	203
+#define FRAME_attack28        	204
+#define FRAME_attack29        	205
+#define FRAME_attack30        	206
+#define FRAME_attack31        	207
+#define FRAME_attack32        	208
+#define FRAME_attack33        	209
+#define FRAME_attack34        	210
+#define FRAME_attack35        	211
+#define FRAME_attack36        	212
+#define FRAME_attack37        	213
+#define FRAME_attack38        	214
+#define FRAME_attack39        	215
+#define FRAME_attack40        	216
+#define FRAME_attack41        	217
+#define FRAME_attack42        	218
+#define FRAME_attack43        	219
+#define FRAME_attack44        	220
+#define FRAME_attack45        	221
+#define FRAME_attack46        	222
+#define FRAME_attack47        	223
+#define FRAME_attack48        	224
+#define FRAME_attack49        	225
+#define FRAME_attack50        	226
+#define FRAME_attack51        	227
+#define FRAME_attack52        	228
+#define FRAME_attack53        	229
+#define FRAME_attack54        	230
+#define FRAME_attack55        	231
+#define FRAME_attack56        	232
+#define FRAME_attack57        	233
+#define FRAME_attack58        	234
+#define FRAME_attack59        	235
+#define FRAME_attack60        	236
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_move.c
@@ -1,0 +1,847 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define	STEPSIZE	18
+
+// this is used for communications out of sv_movestep to say what entity
+// is blocking us
+edict_t		*new_bad;			//pmm
+
+/*
+=============
+M_CheckBottom
+
+Returns false if any part of the bottom of the entity is off an edge that
+is not a staircase.
+
+=============
+*/
+int c_yes, c_no;
+
+qboolean M_CheckBottom (edict_t *ent)
+{
+	vec3_t	mins, maxs, start, stop;
+	trace_t	trace;
+	int		x, y;
+	float	mid, bottom;
+	
+	VectorAdd (ent->s.origin, ent->mins, mins);
+	VectorAdd (ent->s.origin, ent->maxs, maxs);
+
+// if all of the points under the corners are solid world, don't bother
+// with the tougher checks
+// the corners must be within 16 of the midpoint
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+	start[2] = mins[2] - 1;
+	if(ent->gravityVector[2] > 0)
+		start[2] = maxs[2] + 1;
+#else
+	start[2] = mins[2] - 1;
+#endif
+//PGM
+
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = x ? maxs[0] : mins[0];
+			start[1] = y ? maxs[1] : mins[1];
+			if (gi.pointcontents (start) != CONTENTS_SOLID)
+				goto realcheck;
+		}
+
+	c_yes++;
+	return true;		// we got out easy
+
+realcheck:
+	c_no++;
+//
+// check it for real...
+//
+	start[2] = mins[2];
+	
+// the midpoint must be within 16 of the bottom
+	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	if(ent->gravityVector[2] < 0)
+	{
+		start[2] = mins[2];
+		stop[2] = start[2] - STEPSIZE - STEPSIZE;
+	}
+	else
+	{
+		start[2] = maxs[2];
+		stop[2] = start[2] + STEPSIZE + STEPSIZE;
+	}
+#else
+	stop[2] = start[2] - 2*STEPSIZE;
+#endif
+//PGM
+
+	trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1.0)
+		return false;
+	mid = bottom = trace.endpos[2];
+	
+// the corners must be within 16 of the midpoint	
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = stop[0] = x ? maxs[0] : mins[0];
+			start[1] = stop[1] = y ? maxs[1] : mins[1];
+			
+			trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+//PGM
+#ifdef ROGUE_GRAVITY
+			// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+			if(ent->gravityVector[2] > 0)
+			{
+				if (trace.fraction != 1.0 && trace.endpos[2] < bottom)
+					bottom = trace.endpos[2];
+				if (trace.fraction == 1.0 || trace.endpos[2] - mid > STEPSIZE)
+					return false;
+			}
+			else
+			{
+				if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+					bottom = trace.endpos[2];
+				if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+					return false;
+			}
+#else
+			if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+				bottom = trace.endpos[2];
+			if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+				return false;
+#endif
+//PGM
+		}
+
+	c_yes++;
+	return true;
+}
+
+//============
+// ROGUE
+qboolean IsBadAhead (edict_t *self, edict_t *bad, vec3_t move)
+{
+	vec3_t	dir;
+	vec3_t	forward;
+	float	dp_bad, dp_move;
+	vec3_t	move_copy;
+
+	VectorCopy (move, move_copy);
+
+	VectorSubtract (bad->s.origin, self->s.origin, dir);
+	VectorNormalize (dir);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	dp_bad = DotProduct (forward, dir);
+
+	VectorNormalize (move_copy);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	dp_move = DotProduct (forward, move_copy);
+
+	if ((dp_bad < 0) && (dp_move < 0))
+		return true;
+	if ((dp_bad > 0) && (dp_move > 0))
+		return true;
+
+	return false;
+/*
+	if(DotProduct(forward, dir) > 0)
+	{
+//		gi.dprintf ("bad ahead...\n");
+		return true;
+	}
+
+//	gi.dprintf ("bad behind...\n");
+	return false;
+	*/
+}
+// ROGUE
+//============
+
+/*
+=============
+SV_movestep
+
+Called by monster program code.
+The move will be adjusted for slopes and stairs, but if the move isn't
+possible, no move is done, false is returned, and
+pr_global_struct->trace_normal is set to the normal of the blocking wall
+=============
+*/
+//FIXME since we need to test end position contents here, can we avoid doing
+//it again later in catagorize position?
+qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
+{
+	float		dz;
+	vec3_t		oldorg, neworg, end;
+	trace_t		trace;
+	int			i;
+	float		stepsize;
+	vec3_t		test;
+	int			contents;
+	edict_t		*current_bad = nil;		// PGM
+	float		minheight;			// pmm
+
+//======
+//PGM
+
+	// PMM - who cares about bad areas if you're dead?
+	if (ent->health > 0)
+	{
+		current_bad = CheckForBadArea(ent);
+		if(current_bad)
+		{
+			ent->bad_area = current_bad;
+			 
+			if(ent->enemy && !strcmp(ent->enemy->classname, "tesla"))
+			{
+				// if the tesla is in front of us, back up...
+				if (IsBadAhead (ent, current_bad, move))
+					VectorScale(move, -1, move);
+			}
+		}
+		else if(ent->bad_area)
+		{
+			// if we're no longer in a bad area, get back to business.
+			ent->bad_area = NULL;
+			if(ent->oldenemy)// && ent->bad_area->owner == ent->enemy)
+			{
+	//			gi.dprintf("resuming being pissed at %s\n", ent->oldenemy->classname);
+				ent->enemy = ent->oldenemy;
+				ent->goalentity = ent->oldenemy;
+				FoundTarget(ent);
+	// FIXME - remove this when ready!!!
+//	if (ent->lastMoveTime == level.time)
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname);
+//	ent->lastMoveTime = level.time;
+	// FIXME
+
+				return true;
+			}
+		}
+	}
+//PGM
+//======
+
+// try the move	
+	VectorCopy (ent->s.origin, oldorg);
+	VectorAdd (ent->s.origin, move, neworg);
+
+// flying monsters don't step up
+	if ( ent->flags & (FL_SWIM | FL_FLY) )
+	{
+	// try one move with vertical motion, then one without
+		for (i=0 ; i<2 ; i++)
+		{
+			VectorAdd (ent->s.origin, move, neworg);
+			if (i == 0 && ent->enemy)
+			{
+				if (!ent->goalentity)
+					ent->goalentity = ent->enemy;
+				dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+				if (ent->goalentity->client)
+				{
+					// we want the carrier to stay a certain distance off the ground, to help prevent him
+					// from shooting his fliers, who spawn in below him
+					//
+					if (!strcmp(ent->classname, "monster_carrier"))
+						minheight = 104;
+					else
+						minheight = 40;
+//					if (dz > 40)
+					if (dz > minheight)
+//	pmm		
+						neworg[2] -= 8;
+					if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+						if (dz < (minheight - 10))
+							neworg[2] += 8;
+				}
+				else
+				{
+					if (dz > 8)
+						neworg[2] -= 8;
+					else if (dz > 0)
+						neworg[2] -= dz;
+					else if (dz < -8)
+						neworg[2] += 8;
+					else
+						neworg[2] += dz;
+				}
+			}
+
+			trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
+	
+			// fly monsters don't enter water voluntarily
+			if (ent->flags & FL_FLY)
+			{
+				if (!ent->waterlevel)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (contents & MASK_WATER)
+						return false;
+				}
+			}
+
+			// swim monsters don't exit water voluntarily
+			if (ent->flags & FL_SWIM)
+			{
+				if (ent->waterlevel < 2)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (!(contents & MASK_WATER))
+						return false;
+				}
+			}
+
+//			if (trace.fraction == 1)
+
+			// PMM - changed above to this
+			if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid))
+			{
+				VectorCopy (trace.endpos, ent->s.origin);
+//=====
+//PGM				
+				if(!current_bad && CheckForBadArea(ent))
+				{
+//						gi.dprintf("Oooh! Bad Area!\n");
+					VectorCopy (oldorg, ent->s.origin);
+				}
+				else
+				{
+					if (relink)
+					{
+						gi.linkentity (ent);
+						G_TouchTriggers (ent);
+					}
+	// FIXME - remove this when ready!!!
+//	if (ent->lastMoveTime == level.time)
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname);
+//	ent->lastMoveTime = level.time;
+	// FIXME
+
+					return true;
+				}
+//PGM
+//=====
+			}
+			
+			if (!ent->enemy)
+				break;
+		}
+		
+		return false;
+	}
+
+// push down from a step height above the wished position
+	if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+		stepsize = STEPSIZE;
+	else
+		stepsize = 1;
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	// trace from 1 stepsize gravityUp to 2 stepsize gravityDown.
+	VectorMA(neworg, -1 * stepsize, ent->gravityVector, neworg);
+	VectorMA(neworg, 2 * stepsize, ent->gravityVector, end);
+#else
+	neworg[2] += stepsize;
+	VectorCopy (neworg, end);
+	end[2] -= stepsize*2;
+#endif
+//PGM
+
+	trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.allsolid)
+		return false;
+
+	if (trace.startsolid)
+	{
+		neworg[2] -= stepsize;
+		trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+		if (trace.allsolid || trace.startsolid)
+			return false;
+	}
+
+
+	// don't go in to water
+	if (ent->waterlevel == 0)
+	{
+//PGM
+#ifdef ROGUE_GRAVITY
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		if(ent->gravityVector[2] > 0)
+			test[2] = trace.endpos[2] + ent->maxs[2] - 1;	
+		else
+			test[2] = trace.endpos[2] + ent->mins[2] + 1;
+#else
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		test[2] = trace.endpos[2] + ent->mins[2] + 1;	
+#endif
+//PGM
+
+		contents = gi.pointcontents(test);
+
+		if (contents & MASK_WATER)
+			return false;
+	}
+
+	if (trace.fraction == 1)
+	{
+	// if monster had the ground pulled out, go ahead and fall
+		if ( ent->flags & FL_PARTIALGROUND )
+		{
+			VectorAdd (ent->s.origin, move, ent->s.origin);
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			ent->groundentity = NULL;
+	// FIXME - remove this when ready!!!
+//	if (ent->lastMoveTime == level.time)
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname);
+//	ent->lastMoveTime = level.time;
+	// FIXME
+
+			return true;
+		}
+	
+		return false;		// walked off an edge
+	}
+
+// check point traces down for dangling corners
+	VectorCopy (trace.endpos, ent->s.origin);
+
+//PGM
+	// PMM - don't bother with bad areas if we're dead
+	if (ent->health > 0)
+	{
+		// use AI_BLOCKED to tell the calling layer that we're now mad at a tesla
+		new_bad = CheckForBadArea(ent);
+		if(!current_bad && new_bad)
+		{
+			if (new_bad->owner)
+			{
+//				if ((g_showlogic) && (g_showlogic->value))
+//					gi.dprintf("Blocked -");
+				if (!strcmp(new_bad->owner->classname, "tesla"))
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("it's a tesla -");
+					if ((!(ent->enemy)) || (!(ent->enemy->inuse)))
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("I don't have a valid enemy, attacking tesla!\n");
+						TargetTesla (ent, new_bad->owner);
+						ent->monsterinfo.aiflags |= AI_BLOCKED;
+					}
+					else if (!strcmp(ent->enemy->classname, "telsa"))
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("but we're already mad at a tesla\n");
+					}
+					else if ((ent->enemy) && (ent->enemy->client))
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("we have a player enemy -");
+						if (visible(ent, ent->enemy))
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//								gi.dprintf ("we can see him -");
+						}
+						else
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//								gi.dprintf ("can't see him, kill the tesla! -");
+							TargetTesla (ent, new_bad->owner);
+							ent->monsterinfo.aiflags |= AI_BLOCKED;
+						}
+					}
+					else
+					{
+//						if ((g_showlogic) && (g_showlogic->value))
+//							gi.dprintf ("the enemy isn't a player, killing tesla -");
+						TargetTesla (ent, new_bad->owner);
+						ent->monsterinfo.aiflags |= AI_BLOCKED;
+					}
+				}
+//				else if ((g_showlogic) && (g_showlogic->value))
+//				{
+//					gi.dprintf(" by non-tesla bad area!");
+//				}
+			}
+//			gi.dprintf ("\n");
+
+			VectorCopy (oldorg, ent->s.origin);
+			return false;
+		}
+	}
+//PGM
+
+	if (!M_CheckBottom (ent))
+	{
+		if ( ent->flags & FL_PARTIALGROUND )
+		{	// entity had floor mostly pulled out from underneath it
+			// and is trying to correct
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+	// FIXME - remove this when ready!!!
+//	if (ent->lastMoveTime == level.time)
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname);
+//	ent->lastMoveTime = level.time;
+	// FIXME
+
+			return true;
+		}
+		VectorCopy (oldorg, ent->s.origin);
+		return false;
+	}
+
+	if ( ent->flags & FL_PARTIALGROUND )
+	{
+		ent->flags &= ~FL_PARTIALGROUND;
+	}
+	ent->groundentity = trace.ent;
+	ent->groundentity_linkcount = trace.ent->linkcount;
+
+// the move is ok
+	if (relink)
+	{
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+	}
+	// FIXME - remove this when ready!!!
+//	if (ent->lastMoveTime == level.time)
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("Duplicate move detected for %s, please tell programmers!\n", ent->classname);
+//	ent->lastMoveTime = level.time;
+	// FIXME
+
+	return true;
+}
+
+
+//============================================================================
+
+/*
+===============
+M_ChangeYaw
+
+===============
+*/
+void M_ChangeYaw (edict_t *ent)
+{
+	float	ideal;
+	float	current;
+	float	move;
+	float	speed;
+	
+	current = anglemod(ent->s.angles[YAW]);
+	ideal = ent->ideal_yaw;
+
+	if (current == ideal)
+		return;
+
+	move = ideal - current;
+	speed = ent->yaw_speed;
+	if (ideal > current)
+	{
+		if (move >= 180)
+			move = move - 360;
+	}
+	else
+	{
+		if (move <= -180)
+			move = move + 360;
+	}
+	if (move > 0)
+	{
+		if (move > speed)
+			move = speed;
+	}
+	else
+	{
+		if (move < -speed)
+			move = -speed;
+	}
+	
+	ent->s.angles[YAW] = anglemod (current + move);
+}
+
+
+/*
+======================
+SV_StepDirection
+
+Turns to the movement direction, and walks the current distance if
+facing it.
+
+======================
+*/
+qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
+{
+	vec3_t		move, oldorigin;
+	float		delta;
+	
+	if(!ent->inuse)	return true;		// PGM g_touchtrigger free problem
+
+	ent->ideal_yaw = yaw;
+	M_ChangeYaw (ent);
+	
+	yaw = yaw*M_PI*2 / 360;
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	VectorCopy (ent->s.origin, oldorigin);
+	if (SV_movestep (ent, move, false))
+	{
+		ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+		if(!ent->inuse)	return true;		// PGM g_touchtrigger free problem
+
+		delta = ent->s.angles[YAW] - ent->ideal_yaw;
+		if (strncmp(ent->classname, "monster_widow", 13))
+		{
+			if (delta > 45 && delta < 315)
+			{		// not turned far enough, so don't take the step
+				VectorCopy (oldorigin, ent->s.origin);
+			}
+		}
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+		return true;
+	}
+	gi.linkentity (ent);
+	G_TouchTriggers (ent);
+	return false;
+}
+
+/*
+======================
+SV_FixCheckBottom
+
+======================
+*/
+void SV_FixCheckBottom (edict_t *ent)
+{
+	ent->flags |= FL_PARTIALGROUND;
+}
+
+
+
+/*
+================
+SV_NewChaseDir
+
+================
+*/
+#define	DI_NODIR	-1
+void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
+{
+	float	deltax,deltay;
+	float	d[3];
+	float	tdir, olddir, turnaround;
+
+	//FIXME: how did we get here with no enemy
+	if (!enemy)
+		return;
+
+	olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
+	turnaround = anglemod(olddir - 180);
+
+	deltax = enemy->s.origin[0] - actor->s.origin[0];
+	deltay = enemy->s.origin[1] - actor->s.origin[1];
+	if (deltax>10)
+		d[1]= 0;
+	else if (deltax<-10)
+		d[1]= 180;
+	else
+		d[1]= DI_NODIR;
+	if (deltay<-10)
+		d[2]= 270;
+	else if (deltay>10)
+		d[2]= 90;
+	else
+		d[2]= DI_NODIR;
+
+// try direct route
+	if (d[1] != DI_NODIR && d[2] != DI_NODIR)
+	{
+		if (d[1] == 0)
+			tdir = d[2] == 90 ? 45 : 315;
+		else
+			tdir = d[2] == 90 ? 135 : 215;
+			
+		if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
+			return;
+	}
+
+// try other directions
+	if ( ((rand()&3) & 1) ||  abs(deltay)>abs(deltax))
+	{
+		tdir=d[1];
+		d[1]=d[2];
+		d[2]=tdir;
+	}
+
+	if (d[1]!=DI_NODIR && d[1]!=turnaround 
+	&& SV_StepDirection(actor, d[1], dist))
+			return;
+
+	if (d[2]!=DI_NODIR && d[2]!=turnaround
+	&& SV_StepDirection(actor, d[2], dist))
+			return;
+
+//ROGUE
+	if(actor->monsterinfo.blocked)
+	{
+		if ((actor->inuse) && (actor->health > 0))
+		{
+			if((actor->monsterinfo.blocked)(actor, dist))
+				return;
+		}
+	}
+//ROGUE
+
+/* there is no direct path to the player, so pick another direction */
+
+	if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
+			return;
+
+	if (rand()&1) 	/*randomly determine direction of search*/
+	{
+		for (tdir=0 ; tdir<=315 ; tdir += 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+	else
+	{
+		for (tdir=315 ; tdir >=0 ; tdir -= 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+
+	if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
+			return;
+
+	actor->ideal_yaw = olddir;		// can't move
+
+// if a bridge was pulled out from underneath a monster, it may not have
+// a valid standing position at all
+
+	if (!M_CheckBottom (actor))
+		SV_FixCheckBottom (actor);
+}
+
+/*
+======================
+SV_CloseEnough
+
+======================
+*/
+qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
+{
+	int		i;
+	
+	for (i=0 ; i<3 ; i++)
+	{
+		if (goal->absmin[i] > ent->absmax[i] + dist)
+			return false;
+		if (goal->absmax[i] < ent->absmin[i] - dist)
+			return false;
+	}
+	return true;
+}
+
+/*
+======================
+M_MoveToGoal
+======================
+*/
+void M_MoveToGoal (edict_t *ent, float dist)
+{
+	edict_t		*goal;
+	
+	goal = ent->goalentity;
+
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return;
+
+// if the next step hits the enemy, return immediately
+	if (ent->enemy &&  SV_CloseEnough (ent, ent->enemy, dist) )
+		return;
+
+// bump around...
+//	if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+// PMM - charging monsters (AI_CHARGING) don't deflect unless they have to
+	if ( (((rand()&3)==1) && !(ent->monsterinfo.aiflags & AI_CHARGING)) || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+	{
+		if (ent->monsterinfo.aiflags & AI_BLOCKED)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("tesla attack detected, not changing direction!\n");
+			ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+			return;
+		}
+		if (ent->inuse)
+			SV_NewChaseDir (ent, goal, dist);
+	}
+}
+
+
+/*
+===============
+M_walkmove
+===============
+*/
+qboolean M_walkmove (edict_t *ent, float yaw, float dist)
+{
+	vec3_t	move;
+	// PMM
+	qboolean	retval;
+	
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return false;
+
+	yaw = yaw*M_PI*2 / 360;
+	
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	// PMM
+	retval = SV_movestep(ent, move, true);
+	ent->monsterinfo.aiflags &= ~AI_BLOCKED;
+	return retval;
+	// pmm
+	//return SV_movestep(ent, move, true);
+}
--- /dev/null
+++ b/rogue/m_move2.c
@@ -1,0 +1,737 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define	STEPSIZE	18
+
+// this is used for communications out of sv_movestep to say what entity
+// is blocking us
+edict_t		*new_bad;			//pmm
+
+/*
+=============
+M_CheckBottom
+
+Returns false if any part of the bottom of the entity is off an edge that
+is not a staircase.
+
+=============
+*/
+int c_yes, c_no;
+
+qboolean M_CheckBottom (edict_t *ent)
+{
+	vec3_t	mins, maxs, start, stop;
+	trace_t	trace;
+	int		x, y;
+	float	mid, bottom;
+	
+	VectorAdd (ent->s.origin, ent->mins, mins);
+	VectorAdd (ent->s.origin, ent->maxs, maxs);
+
+// if all of the points under the corners are solid world, don't bother
+// with the tougher checks
+// the corners must be within 16 of the midpoint
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+	start[2] = mins[2] - 1;
+	if(ent->gravityVector[2] > 0)
+		start[2] = maxs[2] + 1;
+#else
+	start[2] = mins[2] - 1;
+#endif
+//PGM
+
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = x ? maxs[0] : mins[0];
+			start[1] = y ? maxs[1] : mins[1];
+			if (gi.pointcontents (start) != CONTENTS_SOLID)
+				goto realcheck;
+		}
+
+	c_yes++;
+	return true;		// we got out easy
+
+realcheck:
+	c_no++;
+//
+// check it for real...
+//
+	start[2] = mins[2];
+	
+// the midpoint must be within 16 of the bottom
+	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	if(ent->gravityVector[2] < 0)
+	{
+		start[2] = mins[2];
+		stop[2] = start[2] - STEPSIZE - STEPSIZE;
+	}
+	else
+	{
+		start[2] = maxs[2];
+		stop[2] = start[2] + STEPSIZE + STEPSIZE;
+	}
+#else
+	stop[2] = start[2] - 2*STEPSIZE;
+#endif
+//PGM
+
+	trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1.0)
+		return false;
+	mid = bottom = trace.endpos[2];
+	
+// the corners must be within 16 of the midpoint	
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = stop[0] = x ? maxs[0] : mins[0];
+			start[1] = stop[1] = y ? maxs[1] : mins[1];
+			
+			trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+//PGM
+#ifdef ROGUE_GRAVITY
+			// FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors
+			if(ent->gravityVector[2] > 0)
+			{
+				if (trace.fraction != 1.0 && trace.endpos[2] < bottom)
+					bottom = trace.endpos[2];
+				if (trace.fraction == 1.0 || trace.endpos[2] - mid > STEPSIZE)
+					return false;
+			}
+			else
+			{
+				if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+					bottom = trace.endpos[2];
+				if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+					return false;
+			}
+#else
+			if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+				bottom = trace.endpos[2];
+			if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+				return false;
+#endif
+//PGM
+		}
+
+	c_yes++;
+	return true;
+}
+
+
+/*
+=============
+SV_movestep
+
+Called by monster program code.
+The move will be adjusted for slopes and stairs, but if the move isn't
+possible, no move is done, false is returned, and
+pr_global_struct->trace_normal is set to the normal of the blocking wall
+=============
+*/
+//FIXME since we need to test end position contents here, can we avoid doing
+//it again later in catagorize position?
+qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
+{
+	float		dz;
+	vec3_t		oldorg, neworg, end;
+	trace_t		trace;
+	int			i;
+	float		stepsize;
+	vec3_t		test;
+	int			contents;
+	edict_t		*current_bad;		// PGM
+	float		minheight;			// pmm
+
+//======
+//PGM
+	current_bad = CheckForBadArea(ent);
+	if(current_bad)
+	{
+//		gi.dprintf("in bad area\n");
+		ent->bad_area = current_bad;
+		 
+		if(ent->enemy && !strcmp(ent->enemy->classname, "tesla"))
+		{
+//			gi.dprintf("%s  -->>  ", vtos(move));
+			VectorScale(move, -1, move);
+//			gi.dprintf("%s\n", vtos(move));
+		}
+	}
+	else if(ent->bad_area)
+	{
+		// if we're no longer in a bad area, get back to business.
+		ent->bad_area = NULL;
+		if(ent->oldenemy)// && ent->bad_area->owner == ent->enemy)
+		{
+//			gi.dprintf("resuming being pissed at %s\n", ent->oldenemy->classname);
+			ent->enemy = ent->oldenemy;
+			ent->goalentity = ent->oldenemy;
+			FoundTarget(ent);
+			return true;
+		}
+	}
+//PGM
+//======
+
+// try the move	
+	VectorCopy (ent->s.origin, oldorg);
+	VectorAdd (ent->s.origin, move, neworg);
+
+// flying monsters don't step up
+	if ( ent->flags & (FL_SWIM | FL_FLY) )
+	{
+	// try one move with vertical motion, then one without
+		for (i=0 ; i<2 ; i++)
+		{
+			VectorAdd (ent->s.origin, move, neworg);
+			if (i == 0 && ent->enemy)
+			{
+				if (!ent->goalentity)
+					ent->goalentity = ent->enemy;
+				dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+				if (ent->goalentity->client)
+				{
+					// we want the carrier to stay a certain distance off the ground, to help prevent him
+					// from shooting his fliers, who spawn in below him
+					//
+					if (!strcmp(ent->classname, "monster_carrier"))
+						minheight = 104;
+					else
+						minheight = 40;
+//					if (dz > 40)
+					if (dz > minheight)
+//	pmm		
+						neworg[2] -= 8;
+					if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+						if (dz < (minheight - 10))
+							neworg[2] += 8;
+				}
+				else
+				{
+					if (dz > 8)
+						neworg[2] -= 8;
+					else if (dz > 0)
+						neworg[2] -= dz;
+					else if (dz < -8)
+						neworg[2] += 8;
+					else
+						neworg[2] += dz;
+				}
+			}
+
+			trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
+	
+			// fly monsters don't enter water voluntarily
+			if (ent->flags & FL_FLY)
+			{
+				if (!ent->waterlevel)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (contents & MASK_WATER)
+						return false;
+				}
+			}
+
+			// swim monsters don't exit water voluntarily
+			if (ent->flags & FL_SWIM)
+			{
+				if (ent->waterlevel < 2)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (!(contents & MASK_WATER))
+						return false;
+				}
+			}
+
+//			if (trace.fraction == 1)
+
+			// PMM - changed above to this
+			if ((trace.fraction == 1) && (!trace.allsolid) && (!trace.startsolid))
+			{
+				VectorCopy (trace.endpos, ent->s.origin);
+//=====
+//PGM				
+				if(!current_bad && CheckForBadArea(ent))
+				{
+//						gi.dprintf("Oooh! Bad Area!\n");
+					VectorCopy (oldorg, ent->s.origin);
+				}
+				else
+				{
+					if (relink)
+					{
+						gi.linkentity (ent);
+						G_TouchTriggers (ent);
+					}
+					return true;
+				}
+//PGM
+//=====
+			}
+			
+			if (!ent->enemy)
+				break;
+		}
+		
+		return false;
+	}
+
+// push down from a step height above the wished position
+	if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+		stepsize = STEPSIZE;
+	else
+		stepsize = 1;
+
+//PGM
+#ifdef ROGUE_GRAVITY
+	// trace from 1 stepsize gravityUp to 2 stepsize gravityDown.
+	VectorMA(neworg, -1 * stepsize, ent->gravityVector, neworg);
+	VectorMA(neworg, 2 * stepsize, ent->gravityVector, end);
+#else
+	neworg[2] += stepsize;
+	VectorCopy (neworg, end);
+	end[2] -= stepsize*2;
+#endif
+//PGM
+
+	trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.allsolid)
+		return false;
+
+	if (trace.startsolid)
+	{
+		neworg[2] -= stepsize;
+		trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+		if (trace.allsolid || trace.startsolid)
+			return false;
+	}
+
+
+	// don't go in to water
+	if (ent->waterlevel == 0)
+	{
+//PGM
+#ifdef ROGUE_GRAVITY
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		if(ent->gravityVector[2] > 0)
+			test[2] = trace.endpos[2] + ent->maxs[2] - 1;	
+		else
+			test[2] = trace.endpos[2] + ent->mins[2] + 1;
+#else
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		test[2] = trace.endpos[2] + ent->mins[2] + 1;	
+#endif
+//PGM
+
+		contents = gi.pointcontents(test);
+
+		if (contents & MASK_WATER)
+			return false;
+	}
+
+	if (trace.fraction == 1)
+	{
+	// if monster had the ground pulled out, go ahead and fall
+		if ( ent->flags & FL_PARTIALGROUND )
+		{
+			VectorAdd (ent->s.origin, move, ent->s.origin);
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			ent->groundentity = NULL;
+			return true;
+		}
+	
+		return false;		// walked off an edge
+	}
+
+// check point traces down for dangling corners
+	VectorCopy (trace.endpos, ent->s.origin);
+
+//PGM
+	new_bad = CheckForBadArea(ent);
+	if(!current_bad && new_bad)
+	{
+		if (new_bad->owner)
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf("Blocked -");
+			if (!strcmp(new_bad->owner->classname, "tesla"))
+			{
+				if ((g_showlogic) && (g_showlogic->value))
+					gi.dprintf ("it's a tesla -");
+				if ((!(ent->enemy)) || (!(ent->enemy->inuse)))
+				{
+					if ((g_showlogic) && (g_showlogic->value))
+						gi.dprintf ("I don't have a valid enemy!\n");
+				}
+				else if (!strcmp(ent->enemy->classname, "telsa"))
+				{
+					if ((g_showlogic) && (g_showlogic->value))
+						gi.dprintf ("but we're already mad at a tesla\n");
+				}
+				else if ((ent->enemy) && (ent->enemy->client))
+				{
+					if ((g_showlogic) && (g_showlogic->value))
+						gi.dprintf ("we have a player enemy -");
+					if (visible(ent, ent->enemy))
+					{
+						if ((g_showlogic) && (g_showlogic->value))
+							gi.dprintf ("we can see him -");
+					}
+					else
+					{
+						if ((g_showlogic) && (g_showlogic->value))
+							gi.dprintf ("can't see him, kill the tesla! -");
+					}
+				}
+				else
+				{
+					if ((g_showlogic) && (g_showlogic->value))
+						gi.dprintf ("the enemy isn't a player -");
+				}
+			}
+		}
+		gi.dprintf ("\n");
+
+
+		VectorCopy (oldorg, ent->s.origin);
+		return false;
+	}
+//PGM
+
+	if (!M_CheckBottom (ent))
+	{
+		if ( ent->flags & FL_PARTIALGROUND )
+		{	// entity had floor mostly pulled out from underneath it
+			// and is trying to correct
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			return true;
+		}
+		VectorCopy (oldorg, ent->s.origin);
+		return false;
+	}
+
+	if ( ent->flags & FL_PARTIALGROUND )
+	{
+		ent->flags &= ~FL_PARTIALGROUND;
+	}
+	ent->groundentity = trace.ent;
+	ent->groundentity_linkcount = trace.ent->linkcount;
+
+// the move is ok
+	if (relink)
+	{
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+	}
+	return true;
+}
+
+
+//============================================================================
+
+/*
+===============
+M_ChangeYaw
+
+===============
+*/
+void M_ChangeYaw (edict_t *ent)
+{
+	float	ideal;
+	float	current;
+	float	move;
+	float	speed;
+	
+	current = anglemod(ent->s.angles[YAW]);
+	ideal = ent->ideal_yaw;
+
+	if (current == ideal)
+		return;
+
+	move = ideal - current;
+	speed = ent->yaw_speed;
+	if (ideal > current)
+	{
+		if (move >= 180)
+			move = move - 360;
+	}
+	else
+	{
+		if (move <= -180)
+			move = move + 360;
+	}
+	if (move > 0)
+	{
+		if (move > speed)
+			move = speed;
+	}
+	else
+	{
+		if (move < -speed)
+			move = -speed;
+	}
+	
+	ent->s.angles[YAW] = anglemod (current + move);
+}
+
+
+/*
+======================
+SV_StepDirection
+
+Turns to the movement direction, and walks the current distance if
+facing it.
+
+======================
+*/
+qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
+{
+	vec3_t		move, oldorigin;
+	float		delta;
+	
+	if(!ent->inuse)	return true;		// PGM g_touchtrigger free problem
+
+	ent->ideal_yaw = yaw;
+	M_ChangeYaw (ent);
+	
+	yaw = yaw*M_PI*2 / 360;
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	VectorCopy (ent->s.origin, oldorigin);
+	if (SV_movestep (ent, move, false))
+	{
+		if(!ent->inuse)	return true;		// PGM g_touchtrigger free problem
+
+		delta = ent->s.angles[YAW] - ent->ideal_yaw;
+		if (strncmp(ent->classname, "monster_widow", 13))
+		{
+			if (delta > 45 && delta < 315)
+			{		// not turned far enough, so don't take the step
+				VectorCopy (oldorigin, ent->s.origin);
+			}
+		}
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+		return true;
+	}
+	gi.linkentity (ent);
+	G_TouchTriggers (ent);
+	return false;
+}
+
+/*
+======================
+SV_FixCheckBottom
+
+======================
+*/
+void SV_FixCheckBottom (edict_t *ent)
+{
+	ent->flags |= FL_PARTIALGROUND;
+}
+
+
+
+/*
+================
+SV_NewChaseDir
+
+================
+*/
+#define	DI_NODIR	-1
+void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
+{
+	float	deltax,deltay;
+	float	d[3];
+	float	tdir, olddir, turnaround;
+
+	//FIXME: how did we get here with no enemy
+	if (!enemy)
+		return;
+
+	olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
+	turnaround = anglemod(olddir - 180);
+
+	deltax = enemy->s.origin[0] - actor->s.origin[0];
+	deltay = enemy->s.origin[1] - actor->s.origin[1];
+	if (deltax>10)
+		d[1]= 0;
+	else if (deltax<-10)
+		d[1]= 180;
+	else
+		d[1]= DI_NODIR;
+	if (deltay<-10)
+		d[2]= 270;
+	else if (deltay>10)
+		d[2]= 90;
+	else
+		d[2]= DI_NODIR;
+
+// try direct route
+	if (d[1] != DI_NODIR && d[2] != DI_NODIR)
+	{
+		if (d[1] == 0)
+			tdir = d[2] == 90 ? 45 : 315;
+		else
+			tdir = d[2] == 90 ? 135 : 215;
+			
+		if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
+			return;
+	}
+
+// try other directions
+	if ( ((rand()&3) & 1) ||  abs(deltay)>abs(deltax))
+	{
+		tdir=d[1];
+		d[1]=d[2];
+		d[2]=tdir;
+	}
+
+	if (d[1]!=DI_NODIR && d[1]!=turnaround 
+	&& SV_StepDirection(actor, d[1], dist))
+			return;
+
+	if (d[2]!=DI_NODIR && d[2]!=turnaround
+	&& SV_StepDirection(actor, d[2], dist))
+			return;
+
+//ROGUE
+	if(actor->monsterinfo.blocked)
+	{
+		if((actor->monsterinfo.blocked)(actor, dist))
+			return;
+	}
+//ROGUE
+
+/* there is no direct path to the player, so pick another direction */
+
+	if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
+			return;
+
+	if (rand()&1) 	/*randomly determine direction of search*/
+	{
+		for (tdir=0 ; tdir<=315 ; tdir += 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+	else
+	{
+		for (tdir=315 ; tdir >=0 ; tdir -= 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+
+	if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
+			return;
+
+	actor->ideal_yaw = olddir;		// can't move
+
+// if a bridge was pulled out from underneath a monster, it may not have
+// a valid standing position at all
+
+	if (!M_CheckBottom (actor))
+		SV_FixCheckBottom (actor);
+}
+
+/*
+======================
+SV_CloseEnough
+
+======================
+*/
+qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
+{
+	int		i;
+	
+	for (i=0 ; i<3 ; i++)
+	{
+		if (goal->absmin[i] > ent->absmax[i] + dist)
+			return false;
+		if (goal->absmax[i] < ent->absmin[i] - dist)
+			return false;
+	}
+	return true;
+}
+
+/*
+======================
+M_MoveToGoal
+======================
+*/
+void M_MoveToGoal (edict_t *ent, float dist)
+{
+	edict_t		*goal;
+	
+	goal = ent->goalentity;
+
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return;
+
+// if the next step hits the enemy, return immediately
+	if (ent->enemy &&  SV_CloseEnough (ent, ent->enemy, dist) )
+		return;
+
+// bump around...
+//	if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+// PMM - charging monsters (AI_CHARGING) don't deflect unless they have to
+	if ( (((rand()&3)==1) && !(ent->monsterinfo.aiflags & AI_CHARGING)) || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+	{
+		if (ent->inuse)
+			SV_NewChaseDir (ent, goal, dist);
+	}
+}
+
+
+/*
+===============
+M_walkmove
+===============
+*/
+qboolean M_walkmove (edict_t *ent, float yaw, float dist)
+{
+	vec3_t	move;
+	
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return false;
+
+	yaw = yaw*M_PI*2 / 360;
+	
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	return SV_movestep(ent, move, true);
+}
--- /dev/null
+++ b/rogue/m_mutant.c
@@ -1,0 +1,721 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_mutant.h"
+
+
+static int	sound_swing;
+static int	sound_hit;
+static int	sound_hit2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_step1;
+static int	sound_step2;
+static int	sound_step3;
+static int	sound_thud;
+
+//
+// SOUNDS
+//
+
+void mutant_step (edict_t *self)
+{
+	int		n;
+	n = (rand() + 1) % 3;
+	if (n == 0)
+		gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);		
+	else if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+}
+
+void mutant_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void mutant_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void mutant_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+
+//
+// STAND
+//
+
+mframe_t mutant_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 40
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 50
+
+	ai_stand, 0, NULL
+};
+mmove_t mutant_move_stand = {FRAME_stand101, FRAME_stand151, mutant_frames_stand, NULL};
+
+void mutant_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_stand;
+}
+
+
+//
+// IDLE
+//
+
+void mutant_idle_loop (edict_t *self)
+{
+	if (qrandom() < 0.75)
+		self->monsterinfo.nextframe = FRAME_stand155;
+}
+
+mframe_t mutant_frames_idle [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,					// scratch loop start
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, mutant_idle_loop,		// scratch loop end
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t mutant_move_idle = {FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand};
+
+void mutant_idle (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_idle;
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//
+// WALK
+//
+
+void mutant_walk (edict_t *self);
+
+mframe_t mutant_frames_walk [] =
+{
+	ai_walk,	3,		NULL,
+	ai_walk,	1,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	10,		NULL,
+	ai_walk,	13,		NULL,
+	ai_walk,	10,		NULL,
+	ai_walk,	0,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	6,		NULL,
+	ai_walk,	16,		NULL,
+	ai_walk,	15,		NULL,
+	ai_walk,	6,		NULL
+};
+mmove_t mutant_move_walk = {FRAME_walk05, FRAME_walk16, mutant_frames_walk, NULL};
+
+void mutant_walk_loop (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_walk;
+}
+
+mframe_t mutant_frames_start_walk [] =
+{
+	ai_walk,	5,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	-2,		NULL,
+	ai_walk,	1,		NULL
+};
+mmove_t mutant_move_start_walk = {FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop};
+
+void mutant_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_start_walk;
+}
+
+
+//
+// RUN
+//
+
+mframe_t mutant_frames_run [] =
+{
+	ai_run,	40,		NULL,
+	ai_run,	40,		mutant_step,
+	ai_run,	24,		NULL,
+	ai_run,	5,		mutant_step,
+	ai_run,	17,		NULL,
+	ai_run,	10,		NULL
+};
+mmove_t mutant_move_run = {FRAME_run03, FRAME_run08, mutant_frames_run, NULL};
+
+void mutant_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &mutant_move_stand;
+	else
+		self->monsterinfo.currentmove = &mutant_move_run;
+}
+
+
+//
+// MELEE
+//
+
+void mutant_hit_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void mutant_hit_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void mutant_check_refire (edict_t *self)
+{
+	if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attack09;
+}
+
+mframe_t mutant_frames_attack [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	mutant_hit_left,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	mutant_hit_right,
+	ai_charge,	0,	mutant_check_refire
+};
+mmove_t mutant_move_attack = {FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run};
+
+void mutant_melee (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_attack;
+}
+
+
+//
+// ATTACK
+//
+
+void mutant_jump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->health <= 0)
+	{
+		self->touch = NULL;
+		return;
+	}
+
+	if (other->takedamage)
+	{
+		if (VectorLength(self->velocity) > 400)
+		{
+			vec3_t	point;
+			vec3_t	normal;
+			int		damage;
+
+			VectorCopy (self->velocity, normal);
+			VectorNormalize(normal);
+			VectorMA (self->s.origin, self->maxs[0], normal, point);
+			damage = 40 + 10 * qrandom();
+			T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_UNKNOWN);
+		}
+	}
+
+	if (!M_CheckBottom (self))
+	{
+		if (self->groundentity)
+		{
+			self->monsterinfo.nextframe = FRAME_attack02;
+			self->touch = NULL;
+		}
+		return;
+	}
+
+	self->touch = NULL;
+}
+
+void mutant_jump_takeoff (edict_t *self)
+{
+	vec3_t	forward;
+
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	self->s.origin[2] += 1;
+	VectorScale (forward, 600, self->velocity);
+	self->velocity[2] = 250;
+	self->groundentity = NULL;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->monsterinfo.attack_finished = level.time + 3;
+	self->touch = mutant_jump_touch;
+}
+
+void mutant_check_landing (edict_t *self)
+{
+	if (self->groundentity)
+	{
+		gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+		self->monsterinfo.attack_finished = 0;
+		self->monsterinfo.aiflags &= ~AI_DUCKED;
+		return;
+	}
+
+	if (level.time > self->monsterinfo.attack_finished)
+		self->monsterinfo.nextframe = FRAME_attack02;
+	else
+		self->monsterinfo.nextframe = FRAME_attack05;
+}
+
+mframe_t mutant_frames_jump [] =
+{
+	ai_charge,	 0,	NULL,
+	ai_charge,	17,	NULL,
+	ai_charge,	15,	mutant_jump_takeoff,
+	ai_charge,	15,	NULL,
+	ai_charge,	15,	mutant_check_landing,
+	ai_charge,	 0,	NULL,
+	ai_charge,	 3,	NULL,
+	ai_charge,	 0,	NULL
+};
+mmove_t mutant_move_jump = {FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run};
+
+void mutant_jump (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_jump;
+}
+
+
+//
+// CHECKATTACK
+//
+
+qboolean mutant_check_melee (edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+		return true;
+	return false;
+}
+
+qboolean mutant_check_jump (edict_t *self)
+{
+	vec3_t	v;
+	float	distance;
+
+	if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+		return false;
+
+	if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+		return false;
+
+	v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+	v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+	v[2] = 0;
+	distance = VectorLength(v);
+
+	if (distance < 100)
+		return false;
+	if (distance > 100)
+	{
+		if (qrandom() < 0.9)
+			return false;
+	}
+
+	return true;
+}
+
+qboolean mutant_checkattack (edict_t *self)
+{
+	if (!self->enemy || self->enemy->health <= 0)
+		return false;
+
+	if (mutant_check_melee(self))
+	{
+		self->monsterinfo.attack_state = AS_MELEE;
+		return true;
+	}
+
+	if (mutant_check_jump(self))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		// FIXME play a jump sound here
+		return true;
+	}
+
+	return false;
+}
+
+
+//
+// PAIN
+//
+
+mframe_t mutant_frames_pain1 [] =
+{
+	ai_move,	4,	NULL,
+	ai_move,	-3,	NULL,
+	ai_move,	-8,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	5,	NULL
+};
+mmove_t mutant_move_pain1 = {FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run};
+
+mframe_t mutant_frames_pain2 [] =
+{
+	ai_move,	-24,NULL,
+	ai_move,	11,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	6,	NULL,
+	ai_move,	4,	NULL
+};
+mmove_t mutant_move_pain2 = {FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run};
+
+mframe_t mutant_frames_pain3 [] =
+{
+	ai_move,	-22,NULL,
+	ai_move,	3,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	6,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	1,	NULL
+};
+mmove_t mutant_move_pain3 = {FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run};
+
+void mutant_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+	if (r < 0.33)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain1;
+	}
+	else if (r < 0.66)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain3;
+	}
+}
+
+
+//
+// DEATH
+//
+
+void mutant_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	gi.linkentity (self);
+
+	M_FlyCheck (self);
+}
+
+mframe_t mutant_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t mutant_move_death1 = {FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead};
+
+mframe_t mutant_frames_death2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead};
+
+void mutant_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum = 1;
+
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &mutant_move_death1;
+	else
+		self->monsterinfo.currentmove = &mutant_move_death2;
+}
+
+
+
+//================
+//ROGUE
+void mutant_jump_down (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void mutant_jump_up (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 200, forward, self->velocity);
+	VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+void mutant_jump_wait_land (edict_t *self)
+{
+	if(self->groundentity == NULL)
+		self->monsterinfo.nextframe = self->s.frame;
+	else 
+		self->monsterinfo.nextframe = self->s.frame + 1;
+}
+
+mframe_t mutant_frames_jump_up [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -8, mutant_jump_up,
+	ai_move, 0, mutant_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t mutant_move_jump_up = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_up, mutant_run };
+
+mframe_t mutant_frames_jump_down [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, mutant_jump_down,
+	ai_move, 0, mutant_jump_wait_land,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t mutant_move_jump_down = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_down, mutant_run };
+
+void mutant_jump_updown (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	if(self->enemy->s.origin[2] > self->s.origin[2])
+		self->monsterinfo.currentmove = &mutant_move_jump_up;
+	else
+		self->monsterinfo.currentmove = &mutant_move_jump_down;
+}
+
+/*
+===
+Blocked
+===
+*/
+qboolean mutant_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkjump (self, dist, 256, 68))
+	{
+		mutant_jump_updown (self);
+		return true;
+	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+	return false;
+}
+//ROGUE
+//================
+
+//
+// SPAWN
+//
+
+/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_mutant (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_swing = gi.soundindex ("mutant/mutatck1.wav");
+	sound_hit = gi.soundindex ("mutant/mutatck2.wav");
+	sound_hit2 = gi.soundindex ("mutant/mutatck3.wav");
+	sound_death = gi.soundindex ("mutant/mutdeth1.wav");
+	sound_idle = gi.soundindex ("mutant/mutidle1.wav");
+	sound_pain1 = gi.soundindex ("mutant/mutpain1.wav");
+	sound_pain2 = gi.soundindex ("mutant/mutpain2.wav");
+	sound_sight = gi.soundindex ("mutant/mutsght1.wav");
+	sound_search = gi.soundindex ("mutant/mutsrch1.wav");
+	sound_step1 = gi.soundindex ("mutant/step1.wav");
+	sound_step2 = gi.soundindex ("mutant/step2.wav");
+	sound_step3 = gi.soundindex ("mutant/step3.wav");
+	sound_thud = gi.soundindex ("mutant/thud1.wav");
+	
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/mutant/tris.md2");
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 48);
+
+	self->health = 300;
+	self->gib_health = -120;
+	self->mass = 300;
+
+	self->pain = mutant_pain;
+	self->die = mutant_die;
+
+	self->monsterinfo.stand = mutant_stand;
+	self->monsterinfo.walk = mutant_walk;
+	self->monsterinfo.run = mutant_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = mutant_jump;
+	self->monsterinfo.melee = mutant_melee;
+	self->monsterinfo.sight = mutant_sight;
+	self->monsterinfo.search = mutant_search;
+	self->monsterinfo.idle = mutant_idle;
+	self->monsterinfo.checkattack = mutant_checkattack;
+	self->monsterinfo.blocked = mutant_blocked;			// PGM
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &mutant_move_stand;
+
+	self->monsterinfo.scale = MODEL_SCALE;
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_mutant.h
@@ -1,0 +1,161 @@
+// G:\quake2\baseq2\models/monsters/mutant
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attack01        	0
+#define FRAME_attack02        	1
+#define FRAME_attack03        	2
+#define FRAME_attack04        	3
+#define FRAME_attack05        	4
+#define FRAME_attack06        	5
+#define FRAME_attack07        	6
+#define FRAME_attack08        	7
+#define FRAME_attack09        	8
+#define FRAME_attack10        	9
+#define FRAME_attack11        	10
+#define FRAME_attack12        	11
+#define FRAME_attack13        	12
+#define FRAME_attack14        	13
+#define FRAME_attack15        	14
+#define FRAME_death101        	15
+#define FRAME_death102        	16
+#define FRAME_death103        	17
+#define FRAME_death104        	18
+#define FRAME_death105        	19
+#define FRAME_death106        	20
+#define FRAME_death107        	21
+#define FRAME_death108        	22
+#define FRAME_death109        	23
+#define FRAME_death201        	24
+#define FRAME_death202        	25
+#define FRAME_death203        	26
+#define FRAME_death204        	27
+#define FRAME_death205        	28
+#define FRAME_death206        	29
+#define FRAME_death207        	30
+#define FRAME_death208        	31
+#define FRAME_death209        	32
+#define FRAME_death210        	33
+#define FRAME_pain101         	34
+#define FRAME_pain102         	35
+#define FRAME_pain103         	36
+#define FRAME_pain104         	37
+#define FRAME_pain105         	38
+#define FRAME_pain201         	39
+#define FRAME_pain202         	40
+#define FRAME_pain203         	41
+#define FRAME_pain204         	42
+#define FRAME_pain205         	43
+#define FRAME_pain206         	44
+#define FRAME_pain301         	45
+#define FRAME_pain302         	46
+#define FRAME_pain303         	47
+#define FRAME_pain304         	48
+#define FRAME_pain305         	49
+#define FRAME_pain306         	50
+#define FRAME_pain307         	51
+#define FRAME_pain308         	52
+#define FRAME_pain309         	53
+#define FRAME_pain310         	54
+#define FRAME_pain311         	55
+#define FRAME_run03           	56
+#define FRAME_run04           	57
+#define FRAME_run05           	58
+#define FRAME_run06           	59
+#define FRAME_run07           	60
+#define FRAME_run08           	61
+#define FRAME_stand101        	62
+#define FRAME_stand102        	63
+#define FRAME_stand103        	64
+#define FRAME_stand104        	65
+#define FRAME_stand105        	66
+#define FRAME_stand106        	67
+#define FRAME_stand107        	68
+#define FRAME_stand108        	69
+#define FRAME_stand109        	70
+#define FRAME_stand110        	71
+#define FRAME_stand111        	72
+#define FRAME_stand112        	73
+#define FRAME_stand113        	74
+#define FRAME_stand114        	75
+#define FRAME_stand115        	76
+#define FRAME_stand116        	77
+#define FRAME_stand117        	78
+#define FRAME_stand118        	79
+#define FRAME_stand119        	80
+#define FRAME_stand120        	81
+#define FRAME_stand121        	82
+#define FRAME_stand122        	83
+#define FRAME_stand123        	84
+#define FRAME_stand124        	85
+#define FRAME_stand125        	86
+#define FRAME_stand126        	87
+#define FRAME_stand127        	88
+#define FRAME_stand128        	89
+#define FRAME_stand129        	90
+#define FRAME_stand130        	91
+#define FRAME_stand131        	92
+#define FRAME_stand132        	93
+#define FRAME_stand133        	94
+#define FRAME_stand134        	95
+#define FRAME_stand135        	96
+#define FRAME_stand136        	97
+#define FRAME_stand137        	98
+#define FRAME_stand138        	99
+#define FRAME_stand139        	100
+#define FRAME_stand140        	101
+#define FRAME_stand141        	102
+#define FRAME_stand142        	103
+#define FRAME_stand143        	104
+#define FRAME_stand144        	105
+#define FRAME_stand145        	106
+#define FRAME_stand146        	107
+#define FRAME_stand147        	108
+#define FRAME_stand148        	109
+#define FRAME_stand149        	110
+#define FRAME_stand150        	111
+#define FRAME_stand151        	112
+#define FRAME_stand152        	113
+#define FRAME_stand153        	114
+#define FRAME_stand154        	115
+#define FRAME_stand155        	116
+#define FRAME_stand156        	117
+#define FRAME_stand157        	118
+#define FRAME_stand158        	119
+#define FRAME_stand159        	120
+#define FRAME_stand160        	121
+#define FRAME_stand161        	122
+#define FRAME_stand162        	123
+#define FRAME_stand163        	124
+#define FRAME_stand164        	125
+#define FRAME_walk01          	126
+#define FRAME_walk02          	127
+#define FRAME_walk03          	128
+#define FRAME_walk04          	129
+#define FRAME_walk05          	130
+#define FRAME_walk06          	131
+#define FRAME_walk07          	132
+#define FRAME_walk08          	133
+#define FRAME_walk09          	134
+#define FRAME_walk10          	135
+#define FRAME_walk11          	136
+#define FRAME_walk12          	137
+#define FRAME_walk13          	138
+#define FRAME_walk14          	139
+#define FRAME_walk15          	140
+#define FRAME_walk16          	141
+#define FRAME_walk17          	142
+#define FRAME_walk18          	143
+#define FRAME_walk19          	144
+#define FRAME_walk20          	145
+#define FRAME_walk21          	146
+#define FRAME_walk22          	147
+#define FRAME_walk23          	148
+
+#define FRAME_jump01			149
+#define FRAME_jump02			150
+#define FRAME_jump03			151
+#define FRAME_jump04			152
+#define FRAME_jump05			153
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_parasite.c
@@ -1,0 +1,672 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_parasite.h"
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_launch;
+static int	sound_impact;
+static int	sound_suck;
+static int	sound_reelin;
+static int	sound_sight;
+static int	sound_tap;
+static int	sound_scratch;
+static int	sound_search;
+
+
+void parasite_stand (edict_t *self);
+void parasite_start_run (edict_t *self);
+void parasite_run (edict_t *self);
+void parasite_walk (edict_t *self);
+void parasite_start_walk (edict_t *self);
+void parasite_end_fidget (edict_t *self);
+void parasite_do_fidget (edict_t *self);
+void parasite_refidget (edict_t *self);
+
+
+void parasite_launch (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0);
+}
+
+void parasite_reel_in (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0);
+}
+
+void parasite_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void parasite_tap (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0);
+}
+
+void parasite_scratch (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0);
+}
+
+void parasite_search (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0);
+}
+
+
+mframe_t parasite_frames_start_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_start_fidget = {FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget};
+
+mframe_t parasite_frames_fidget [] =
+{	
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_fidget = {FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget};
+
+mframe_t parasite_frames_end_fidget [] =
+{
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_end_fidget = {FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand};
+
+void parasite_end_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void parasite_do_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_fidget;
+}
+
+void parasite_refidget (edict_t *self)
+{ 
+	if (qrandom() <= 0.8)
+		self->monsterinfo.currentmove = &parasite_move_fidget;
+	else
+		self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void parasite_idle (edict_t *self)
+{ 
+	self->monsterinfo.currentmove = &parasite_move_start_fidget;
+}
+
+
+mframe_t parasite_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap
+};
+mmove_t	parasite_move_stand = {FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand};
+
+void parasite_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_stand;
+}
+
+
+mframe_t parasite_frames_run [] =
+{
+	ai_run, 30, NULL,
+	ai_run, 30, NULL,
+	ai_run, 22, NULL,
+	ai_run, 19, NULL,
+	ai_run, 24, NULL,
+	ai_run, 28, NULL,
+	ai_run, 25, NULL
+};
+mmove_t parasite_move_run = {FRAME_run03, FRAME_run09, parasite_frames_run, NULL};
+
+mframe_t parasite_frames_start_run [] =
+{
+	ai_run, 0,	NULL,
+	ai_run, 30, NULL,
+};
+mmove_t parasite_move_start_run = {FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run};
+
+mframe_t parasite_frames_stop_run [] =
+{	
+	ai_run, 20, NULL,
+	ai_run, 20,	NULL,
+	ai_run, 12, NULL,
+	ai_run, 10, NULL,
+	ai_run, 0,  NULL,
+	ai_run, 0,  NULL
+};
+mmove_t parasite_move_stop_run = {FRAME_run10, FRAME_run15, parasite_frames_stop_run, NULL};
+
+void parasite_start_run (edict_t *self)
+{	
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &parasite_move_stand;
+	else
+		self->monsterinfo.currentmove = &parasite_move_start_run;
+}
+
+void parasite_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &parasite_move_stand;
+	else
+		self->monsterinfo.currentmove = &parasite_move_run;
+}
+
+
+mframe_t parasite_frames_walk [] =
+{
+	ai_walk, 30, NULL,
+	ai_walk, 30, NULL,
+	ai_walk, 22, NULL,
+	ai_walk, 19, NULL,
+	ai_walk, 24, NULL,
+	ai_walk, 28, NULL,
+	ai_walk, 25, NULL
+};
+mmove_t parasite_move_walk = {FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk};
+
+mframe_t parasite_frames_start_walk [] =
+{
+	ai_walk, 0,	NULL,
+	ai_walk, 30, parasite_walk
+};
+mmove_t parasite_move_start_walk = {FRAME_run01, FRAME_run02, parasite_frames_start_walk, NULL};
+
+mframe_t parasite_frames_stop_walk [] =
+{	
+	ai_walk, 20, NULL,
+	ai_walk, 20,	NULL,
+	ai_walk, 12, NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t parasite_move_stop_walk = {FRAME_run10, FRAME_run15, parasite_frames_stop_walk, NULL};
+
+void parasite_start_walk (edict_t *self)
+{	
+	self->monsterinfo.currentmove = &parasite_move_start_walk;
+}
+
+void parasite_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_walk;
+}
+
+
+mframe_t parasite_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 6,	NULL,
+	ai_move, 16, NULL,
+	ai_move, -6, NULL,
+	ai_move, -7, NULL,
+	ai_move, 0, NULL
+};
+mmove_t parasite_move_pain1 = {FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run};
+
+void parasite_pain (edict_t *self, edict_t *, float, int)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	self->monsterinfo.currentmove = &parasite_move_pain1;
+}
+
+
+//static qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end)
+qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end)
+{
+	vec3_t	dir, angles;
+
+	// check for max distance
+	VectorSubtract (start, end, dir);
+	if (VectorLength(dir) > 256)
+		return false;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 30)
+		return false;
+
+	return true;
+}
+
+void parasite_drain_attack (edict_t *self)
+{
+	vec3_t	offset, start, f, r, end, dir;
+	trace_t	tr;
+	int damage;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorSet (offset, 24, 0, 6);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	if (!parasite_drain_attack_ok(start, end))
+	{
+		end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+		if (!parasite_drain_attack_ok(start, end))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+			if (!parasite_drain_attack_ok(start, end))
+				return;
+		}
+	}
+	VectorCopy (self->enemy->s.origin, end);
+
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if (tr.ent != self->enemy)
+		return;
+
+	if (self->s.frame == FRAME_drain03)
+	{
+		damage = 5;
+		gi.sound (self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		if (self->s.frame == FRAME_drain04)
+			gi.sound (self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0);
+		damage = 2;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PARASITE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	VectorSubtract (start, end, dir);
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+mframe_t parasite_frames_drain [] =
+{
+	ai_charge, 0,	parasite_launch,
+	ai_charge, 0,	NULL,
+	ai_charge, 15,	parasite_drain_attack,			// Target hits
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, -2,  parasite_drain_attack,			// drain
+	ai_charge, -2,	parasite_drain_attack,			// drain
+	ai_charge, -3,	parasite_drain_attack,			// drain
+	ai_charge, -2,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, -1,  parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_reel_in,				// let go
+	ai_charge, -2,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 0,	NULL
+};
+mmove_t parasite_move_drain = {FRAME_drain01, FRAME_drain18, parasite_frames_drain, parasite_start_run};
+
+
+mframe_t parasite_frames_break [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 2,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 9,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 8,	NULL,
+	ai_charge, 9,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,		// airborne
+	ai_charge, 0,	NULL,		// airborne
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 4,	NULL,
+	ai_charge, 11,	NULL,		
+	ai_charge, -2,	NULL,
+	ai_charge, -5,	NULL,
+	ai_charge, 1,	NULL
+};
+mmove_t parasite_move_break = {FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run};
+
+/*
+=== 
+Break Stuff Ends
+===
+*/
+
+void parasite_attack (edict_t *self)
+{
+//	if (random() <= 0.2)
+//		self->monsterinfo.currentmove = &parasite_move_break;
+//	else
+		self->monsterinfo.currentmove = &parasite_move_drain;
+}
+
+
+//================
+//ROGUE
+void parasite_jump_down (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+void parasite_jump_up (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 200, forward, self->velocity);
+	VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+void parasite_jump_wait_land (edict_t *self)
+{
+	if(self->groundentity == NULL)
+	{
+		self->monsterinfo.nextframe = self->s.frame;
+
+		if(monster_jump_finished (self))
+			self->monsterinfo.nextframe = self->s.frame + 1;
+	}
+	else 
+		self->monsterinfo.nextframe = self->s.frame + 1;
+}
+
+mframe_t parasite_frames_jump_up [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -8, NULL,
+	ai_move, -8, NULL,
+	ai_move, -8, parasite_jump_up,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, parasite_jump_wait_land,
+	ai_move, 0, NULL
+};
+mmove_t parasite_move_jump_up = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_up, parasite_run };
+
+mframe_t parasite_frames_jump_down [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, parasite_jump_down,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, parasite_jump_wait_land,
+	ai_move, 0, NULL
+};
+mmove_t parasite_move_jump_down = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_down, parasite_run };
+
+void parasite_jump (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	if(self->enemy->s.origin[2] > self->s.origin[2])
+		self->monsterinfo.currentmove = &parasite_move_jump_up;
+	else
+		self->monsterinfo.currentmove = &parasite_move_jump_down;
+}
+
+/*
+===
+Blocked
+===
+*/
+qboolean parasite_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkjump (self, dist, 256, 68))
+	{
+		parasite_jump (self);
+		return true;
+	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+	return false;
+}
+//ROGUE
+//================
+
+
+qboolean parasite_checkattack (edict_t *self)
+{
+	vec3_t	f, r, offset, start, end;
+	trace_t	tr;
+	qboolean retval;
+
+	retval = M_CheckAttack (self);
+
+	if (!retval)
+		return false;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorSet (offset, 24, 0, 6);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	if (!parasite_drain_attack_ok(start, end))
+	{
+		end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+		if (!parasite_drain_attack_ok(start, end))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+			if (!parasite_drain_attack_ok(start, end))
+				return false;
+		}
+	}
+	VectorCopy (self->enemy->s.origin, end);
+
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if (tr.ent != self->enemy)
+	{
+		self->monsterinfo.aiflags |= AI_BLOCKED;
+		
+		if(self->monsterinfo.attack)
+			self->monsterinfo.attack(self);
+		
+		self->monsterinfo.aiflags &= ~AI_BLOCKED;
+		return true;
+	}
+	return false;
+}
+
+
+/*
+===
+Death Stuff Starts
+===
+*/
+
+void parasite_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t parasite_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t parasite_move_death = {FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead};
+
+void parasite_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &parasite_move_death;
+}
+
+/*
+===
+End Death Stuff
+===
+*/
+
+/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_parasite (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("parasite/parpain1.wav");	
+	sound_pain2 = gi.soundindex ("parasite/parpain2.wav");	
+	sound_die = gi.soundindex ("parasite/pardeth1.wav");	
+	sound_launch = gi.soundindex("parasite/paratck1.wav");
+	sound_impact = gi.soundindex("parasite/paratck2.wav");
+	sound_suck = gi.soundindex("parasite/paratck3.wav");
+	sound_reelin = gi.soundindex("parasite/paratck4.wav");
+	sound_sight = gi.soundindex("parasite/parsght1.wav");
+	sound_tap = gi.soundindex("parasite/paridle1.wav");
+	sound_scratch = gi.soundindex("parasite/paridle2.wav");
+	sound_search = gi.soundindex("parasite/parsrch1.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/parasite/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 24);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 175;
+	self->gib_health = -50;
+	self->mass = 250;
+
+	self->pain = parasite_pain;
+	self->die = parasite_die;
+
+	self->monsterinfo.stand = parasite_stand;
+	self->monsterinfo.walk = parasite_start_walk;
+	self->monsterinfo.run = parasite_start_run;
+	self->monsterinfo.attack = parasite_attack;
+	self->monsterinfo.sight = parasite_sight;
+	self->monsterinfo.idle = parasite_idle;
+	self->monsterinfo.blocked = parasite_blocked;		// PGM
+	self->monsterinfo.checkattack = parasite_checkattack;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &parasite_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_parasite.h
@@ -1,0 +1,133 @@
+// G:\quake2\baseq2\models/monsters/parasite
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_break01           0
+#define FRAME_break02           1
+#define FRAME_break03           2
+#define FRAME_break04           3
+#define FRAME_break05           4
+#define FRAME_break06           5
+#define FRAME_break07           6
+#define FRAME_break08           7
+#define FRAME_break09           8
+#define FRAME_break10           9
+#define FRAME_break11           10
+#define FRAME_break12           11
+#define FRAME_break13           12
+#define FRAME_break14           13
+#define FRAME_break15           14
+#define FRAME_break16           15
+#define FRAME_break17           16
+#define FRAME_break18           17
+#define FRAME_break19           18
+#define FRAME_break20           19
+#define FRAME_break21           20
+#define FRAME_break22           21
+#define FRAME_break23           22
+#define FRAME_break24           23
+#define FRAME_break25           24
+#define FRAME_break26           25
+#define FRAME_break27           26
+#define FRAME_break28           27
+#define FRAME_break29           28
+#define FRAME_break30           29
+#define FRAME_break31           30
+#define FRAME_break32           31
+#define FRAME_death101          32
+#define FRAME_death102          33
+#define FRAME_death103          34
+#define FRAME_death104          35
+#define FRAME_death105          36
+#define FRAME_death106          37
+#define FRAME_death107          38
+#define FRAME_drain01           39
+#define FRAME_drain02           40
+#define FRAME_drain03           41
+#define FRAME_drain04           42
+#define FRAME_drain05           43
+#define FRAME_drain06           44
+#define FRAME_drain07           45
+#define FRAME_drain08           46
+#define FRAME_drain09           47
+#define FRAME_drain10           48
+#define FRAME_drain11           49
+#define FRAME_drain12           50
+#define FRAME_drain13           51
+#define FRAME_drain14           52
+#define FRAME_drain15           53
+#define FRAME_drain16           54
+#define FRAME_drain17           55
+#define FRAME_drain18           56
+#define FRAME_pain101           57
+#define FRAME_pain102           58
+#define FRAME_pain103           59
+#define FRAME_pain104           60
+#define FRAME_pain105           61
+#define FRAME_pain106           62
+#define FRAME_pain107           63
+#define FRAME_pain108           64
+#define FRAME_pain109           65
+#define FRAME_pain110           66
+#define FRAME_pain111           67
+#define FRAME_run01             68
+#define FRAME_run02             69
+#define FRAME_run03             70
+#define FRAME_run04             71
+#define FRAME_run05             72
+#define FRAME_run06             73
+#define FRAME_run07             74
+#define FRAME_run08             75
+#define FRAME_run09             76
+#define FRAME_run10             77
+#define FRAME_run11             78
+#define FRAME_run12             79
+#define FRAME_run13             80
+#define FRAME_run14             81
+#define FRAME_run15             82
+#define FRAME_stand01           83
+#define FRAME_stand02           84
+#define FRAME_stand03           85
+#define FRAME_stand04           86
+#define FRAME_stand05           87
+#define FRAME_stand06           88
+#define FRAME_stand07           89
+#define FRAME_stand08           90
+#define FRAME_stand09           91
+#define FRAME_stand10           92
+#define FRAME_stand11           93
+#define FRAME_stand12           94
+#define FRAME_stand13           95
+#define FRAME_stand14           96
+#define FRAME_stand15           97
+#define FRAME_stand16           98
+#define FRAME_stand17           99
+#define FRAME_stand18           100
+#define FRAME_stand19           101
+#define FRAME_stand20           102
+#define FRAME_stand21           103
+#define FRAME_stand22           104
+#define FRAME_stand23           105
+#define FRAME_stand24           106
+#define FRAME_stand25           107
+#define FRAME_stand26           108
+#define FRAME_stand27           109
+#define FRAME_stand28           110
+#define FRAME_stand29           111
+#define FRAME_stand30           112
+#define FRAME_stand31           113
+#define FRAME_stand32           114
+#define FRAME_stand33           115
+#define FRAME_stand34           116
+#define FRAME_stand35           117
+
+#define FRAME_jump01			118
+#define FRAME_jump02			119
+#define FRAME_jump03			120
+#define FRAME_jump04			121
+#define FRAME_jump05			122
+#define FRAME_jump06			123
+#define FRAME_jump07			124
+#define FRAME_jump08			125
+
+#define MODEL_SCALE             1.000000
--- /dev/null
+++ b/rogue/m_player.h
@@ -1,0 +1,205 @@
+// G:\quake2\baseq2\models/player_x/frames
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_run1            	40
+#define FRAME_run2            	41
+#define FRAME_run3            	42
+#define FRAME_run4            	43
+#define FRAME_run5            	44
+#define FRAME_run6            	45
+#define FRAME_attack1         	46
+#define FRAME_attack2         	47
+#define FRAME_attack3         	48
+#define FRAME_attack4         	49
+#define FRAME_attack5         	50
+#define FRAME_attack6         	51
+#define FRAME_attack7         	52
+#define FRAME_attack8         	53
+#define FRAME_pain101         	54
+#define FRAME_pain102         	55
+#define FRAME_pain103         	56
+#define FRAME_pain104         	57
+#define FRAME_pain201         	58
+#define FRAME_pain202         	59
+#define FRAME_pain203         	60
+#define FRAME_pain204         	61
+#define FRAME_pain301         	62
+#define FRAME_pain302         	63
+#define FRAME_pain303         	64
+#define FRAME_pain304         	65
+#define FRAME_jump1           	66
+#define FRAME_jump2           	67
+#define FRAME_jump3           	68
+#define FRAME_jump4           	69
+#define FRAME_jump5           	70
+#define FRAME_jump6           	71
+#define FRAME_flip01          	72
+#define FRAME_flip02          	73
+#define FRAME_flip03          	74
+#define FRAME_flip04          	75
+#define FRAME_flip05          	76
+#define FRAME_flip06          	77
+#define FRAME_flip07          	78
+#define FRAME_flip08          	79
+#define FRAME_flip09          	80
+#define FRAME_flip10          	81
+#define FRAME_flip11          	82
+#define FRAME_flip12          	83
+#define FRAME_salute01        	84
+#define FRAME_salute02        	85
+#define FRAME_salute03        	86
+#define FRAME_salute04        	87
+#define FRAME_salute05        	88
+#define FRAME_salute06        	89
+#define FRAME_salute07        	90
+#define FRAME_salute08        	91
+#define FRAME_salute09        	92
+#define FRAME_salute10        	93
+#define FRAME_salute11        	94
+#define FRAME_taunt01         	95
+#define FRAME_taunt02         	96
+#define FRAME_taunt03         	97
+#define FRAME_taunt04         	98
+#define FRAME_taunt05         	99
+#define FRAME_taunt06         	100
+#define FRAME_taunt07         	101
+#define FRAME_taunt08         	102
+#define FRAME_taunt09         	103
+#define FRAME_taunt10         	104
+#define FRAME_taunt11         	105
+#define FRAME_taunt12         	106
+#define FRAME_taunt13         	107
+#define FRAME_taunt14         	108
+#define FRAME_taunt15         	109
+#define FRAME_taunt16         	110
+#define FRAME_taunt17         	111
+#define FRAME_wave01          	112
+#define FRAME_wave02          	113
+#define FRAME_wave03          	114
+#define FRAME_wave04          	115
+#define FRAME_wave05          	116
+#define FRAME_wave06          	117
+#define FRAME_wave07          	118
+#define FRAME_wave08          	119
+#define FRAME_wave09          	120
+#define FRAME_wave10          	121
+#define FRAME_wave11          	122
+#define FRAME_point01         	123
+#define FRAME_point02         	124
+#define FRAME_point03         	125
+#define FRAME_point04         	126
+#define FRAME_point05         	127
+#define FRAME_point06         	128
+#define FRAME_point07         	129
+#define FRAME_point08         	130
+#define FRAME_point09         	131
+#define FRAME_point10         	132
+#define FRAME_point11         	133
+#define FRAME_point12         	134
+#define FRAME_crstnd01        	135
+#define FRAME_crstnd02        	136
+#define FRAME_crstnd03        	137
+#define FRAME_crstnd04        	138
+#define FRAME_crstnd05        	139
+#define FRAME_crstnd06        	140
+#define FRAME_crstnd07        	141
+#define FRAME_crstnd08        	142
+#define FRAME_crstnd09        	143
+#define FRAME_crstnd10        	144
+#define FRAME_crstnd11        	145
+#define FRAME_crstnd12        	146
+#define FRAME_crstnd13        	147
+#define FRAME_crstnd14        	148
+#define FRAME_crstnd15        	149
+#define FRAME_crstnd16        	150
+#define FRAME_crstnd17        	151
+#define FRAME_crstnd18        	152
+#define FRAME_crstnd19        	153
+#define FRAME_crwalk1         	154
+#define FRAME_crwalk2         	155
+#define FRAME_crwalk3         	156
+#define FRAME_crwalk4         	157
+#define FRAME_crwalk5         	158
+#define FRAME_crwalk6         	159
+#define FRAME_crattak1        	160
+#define FRAME_crattak2        	161
+#define FRAME_crattak3        	162
+#define FRAME_crattak4        	163
+#define FRAME_crattak5        	164
+#define FRAME_crattak6        	165
+#define FRAME_crattak7        	166
+#define FRAME_crattak8        	167
+#define FRAME_crattak9        	168
+#define FRAME_crpain1         	169
+#define FRAME_crpain2         	170
+#define FRAME_crpain3         	171
+#define FRAME_crpain4         	172
+#define FRAME_crdeath1        	173
+#define FRAME_crdeath2        	174
+#define FRAME_crdeath3        	175
+#define FRAME_crdeath4        	176
+#define FRAME_crdeath5        	177
+#define FRAME_death101        	178
+#define FRAME_death102        	179
+#define FRAME_death103        	180
+#define FRAME_death104        	181
+#define FRAME_death105        	182
+#define FRAME_death106        	183
+#define FRAME_death201        	184
+#define FRAME_death202        	185
+#define FRAME_death203        	186
+#define FRAME_death204        	187
+#define FRAME_death205        	188
+#define FRAME_death206        	189
+#define FRAME_death301        	190
+#define FRAME_death302        	191
+#define FRAME_death303        	192
+#define FRAME_death304        	193
+#define FRAME_death305        	194
+#define FRAME_death306        	195
+#define FRAME_death307        	196
+#define FRAME_death308        	197
+
+#define MODEL_SCALE		1.000000
+
--- /dev/null
+++ b/rogue/m_rider.h
@@ -1,0 +1,66 @@
+// G:\quake2\baseq2\models/monsters/boss3/rider
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand201        	0
+#define FRAME_stand202        	1
+#define FRAME_stand203        	2
+#define FRAME_stand204        	3
+#define FRAME_stand205        	4
+#define FRAME_stand206        	5
+#define FRAME_stand207        	6
+#define FRAME_stand208        	7
+#define FRAME_stand209        	8
+#define FRAME_stand210        	9
+#define FRAME_stand211        	10
+#define FRAME_stand212        	11
+#define FRAME_stand213        	12
+#define FRAME_stand214        	13
+#define FRAME_stand215        	14
+#define FRAME_stand216        	15
+#define FRAME_stand217        	16
+#define FRAME_stand218        	17
+#define FRAME_stand219        	18
+#define FRAME_stand220        	19
+#define FRAME_stand221        	20
+#define FRAME_stand222        	21
+#define FRAME_stand223        	22
+#define FRAME_stand224        	23
+#define FRAME_stand225        	24
+#define FRAME_stand226        	25
+#define FRAME_stand227        	26
+#define FRAME_stand228        	27
+#define FRAME_stand229        	28
+#define FRAME_stand230        	29
+#define FRAME_stand231        	30
+#define FRAME_stand232        	31
+#define FRAME_stand233        	32
+#define FRAME_stand234        	33
+#define FRAME_stand235        	34
+#define FRAME_stand236        	35
+#define FRAME_stand237        	36
+#define FRAME_stand238        	37
+#define FRAME_stand239        	38
+#define FRAME_stand240        	39
+#define FRAME_stand241        	40
+#define FRAME_stand242        	41
+#define FRAME_stand243        	42
+#define FRAME_stand244        	43
+#define FRAME_stand245        	44
+#define FRAME_stand246        	45
+#define FRAME_stand247        	46
+#define FRAME_stand248        	47
+#define FRAME_stand249        	48
+#define FRAME_stand250        	49
+#define FRAME_stand251        	50
+#define FRAME_stand252        	51
+#define FRAME_stand253        	52
+#define FRAME_stand254        	53
+#define FRAME_stand255        	54
+#define FRAME_stand256        	55
+#define FRAME_stand257        	56
+#define FRAME_stand258        	57
+#define FRAME_stand259        	58
+#define FRAME_stand260        	59
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_soldier.c
@@ -1,0 +1,1777 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_soldier.h"
+
+//ROGUE
+#define RUN_SHOOT		1
+#define CHECK_TARGET	1
+//ROGUE
+
+static int	sound_idle;
+static int	sound_sight1;
+static int	sound_sight2;
+static int	sound_pain_light;
+static int	sound_pain;
+static int	sound_pain_ss;
+static int	sound_death_light;
+static int	sound_death;
+static int	sound_death_ss;
+static int	sound_cock;
+
+void soldier_duck_up (edict_t *self);
+
+void soldier_start_charge (edict_t *self)
+{
+	self->monsterinfo.aiflags |= AI_CHARGING;
+}
+
+void soldier_stop_charge (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_CHARGING;
+}
+
+void soldier_idle (edict_t *self)
+{
+	if (qrandom() > 0.8)
+		gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void soldier_cock (edict_t *self)
+{
+	if (self->s.frame == FRAME_stand322)
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+}
+
+
+// STAND
+
+void soldier_stand (edict_t *self);
+
+mframe_t soldier_frames_stand1 [] =
+{
+	ai_stand, 0, soldier_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand};
+
+mframe_t soldier_frames_stand3 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, soldier_cock,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand};
+
+/*
+mframe_t soldier_frames_stand4 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 4, NULL,
+	ai_stand, 1, NULL,
+	ai_stand, -1, NULL,
+	ai_stand, -2, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand4 = {FRAME_stand401, FRAME_stand452, soldier_frames_stand4, NULL};
+*/
+
+void soldier_stand (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &soldier_move_stand3) || (qrandom() < 0.8))
+		self->monsterinfo.currentmove = &soldier_move_stand1;
+	else
+		self->monsterinfo.currentmove = &soldier_move_stand3;
+}
+
+
+//
+// WALK
+//
+
+void soldier_walk1_random (edict_t *self)
+{
+	if (qrandom() > 0.1)
+		self->monsterinfo.nextframe = FRAME_walk101;
+}
+
+mframe_t soldier_frames_walk1 [] =
+{
+	ai_walk, 3,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, -1, soldier_walk1_random,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, soldier_frames_walk1, NULL};
+
+mframe_t soldier_frames_walk2 [] =
+{
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 9,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 7,  NULL
+};
+mmove_t soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, soldier_frames_walk2, NULL};
+
+void soldier_walk (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &soldier_move_walk1;
+	else
+		self->monsterinfo.currentmove = &soldier_move_walk2;
+}
+
+
+//
+// RUN
+//
+
+void soldier_run (edict_t *self);
+
+mframe_t soldier_frames_start_run [] =
+{
+	ai_run, 7,  NULL,
+	ai_run, 5,  NULL
+};
+mmove_t soldier_move_start_run = {FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run};
+
+#ifdef RUN_SHOOT
+void soldier_fire (edict_t *self, int);
+
+void soldier_fire_run (edict_t *self) {
+	if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy)) {
+		soldier_fire(self, 0);
+	}
+}
+#endif
+
+mframe_t soldier_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 11, monster_done_dodge,
+	ai_run, 11, NULL,
+	ai_run, 16, NULL,
+	ai_run, 10, NULL,
+	ai_run, 15, monster_done_dodge
+};
+
+mmove_t soldier_move_run = {FRAME_run03, FRAME_run08, soldier_frames_run, NULL};
+
+void soldier_run (edict_t *self)
+{
+	monster_done_dodge (self);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &soldier_move_stand1;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &soldier_move_walk1 ||
+		self->monsterinfo.currentmove == &soldier_move_walk2 ||
+		self->monsterinfo.currentmove == &soldier_move_start_run)
+	{
+		self->monsterinfo.currentmove = &soldier_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &soldier_move_start_run;
+	}
+}
+
+
+//
+// PAIN
+//
+
+mframe_t soldier_frames_pain1 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t soldier_move_pain1 = {FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run};
+
+mframe_t soldier_frames_pain2 [] =
+{
+	ai_move, -13, NULL,
+	ai_move, -1,  NULL,
+	ai_move, 2,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL
+};
+mmove_t soldier_move_pain2 = {FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run};
+
+mframe_t soldier_frames_pain3 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, 10, NULL,
+	ai_move, -4, NULL,
+	ai_move, -1, NULL,
+	ai_move, -3, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 4,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t soldier_move_pain3 = {FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run};
+
+mframe_t soldier_frames_pain4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -6,  NULL,
+	ai_move, 8,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 5,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_pain4 = {FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run};
+
+
+void soldier_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum |= 1;
+
+	monster_done_dodge (self);
+	soldier_stop_charge(self);
+
+	// if we're blind firing, this needs to be turned off here
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3)))
+		{
+			// PMM - clear duck flag
+			if (self->monsterinfo.aiflags & AI_DUCKED)
+				monster_duck_up(self);
+			self->monsterinfo.currentmove = &soldier_move_pain4;
+		}
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	n = self->s.skinnum | 1;
+	if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+	else if (n == 3)
+		gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+
+	if (self->velocity[2] > 100)
+	{
+		// PMM - clear duck flag
+		if (self->monsterinfo.aiflags & AI_DUCKED)
+			monster_duck_up(self);
+		self->monsterinfo.currentmove = &soldier_move_pain4;
+//		self->monsterinfo.pausetime = 0;
+		return;
+	}
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+
+	if (r < 0.33)
+		self->monsterinfo.currentmove = &soldier_move_pain1;
+	else if (r < 0.66)
+		self->monsterinfo.currentmove = &soldier_move_pain2;
+	else
+		self->monsterinfo.currentmove = &soldier_move_pain3;
+
+	// PMM - clear duck flag
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		monster_duck_up(self);
+//	self->monsterinfo.pausetime = 0;
+
+}
+
+
+//
+// ATTACK
+//
+
+static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8};
+static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8};
+static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8};
+
+//void soldier_fire (edict_t *self, int flash_number)  PMM
+void soldier_fire (edict_t *self, int in_flash_number)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	aim;
+	vec3_t	dir;
+	vec3_t	end;
+	float	r, u;
+	int		flash_index;
+	int		flash_number;
+#ifdef RUN_SHOOT
+	vec3_t	aim_norm;
+	float	angle;
+#endif
+#ifdef CHECK_TARGET
+	trace_t	tr;
+	vec3_t aim_good;
+#endif
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		return;
+	}
+
+	if (in_flash_number < 0)
+	{
+		flash_number = -1 * in_flash_number;
+	}
+	else
+		flash_number = in_flash_number;
+
+	if (self->s.skinnum < 2)
+		flash_index = blaster_flash[flash_number];
+	else if (self->s.skinnum < 4)
+		flash_index = shotgun_flash[flash_number];
+	else
+		flash_index = machinegun_flash[flash_number];
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start);
+
+	if (flash_number == 5 || flash_number == 6) // he's dead
+	{
+		VectorCopy (forward, aim);
+	}
+	else
+	{
+		VectorCopy (self->enemy->s.origin, end);
+		end[2] += self->enemy->viewheight;
+		VectorSubtract (end, start, aim);
+#ifdef CHECK_TARGET
+		VectorCopy (end, aim_good);
+#endif
+#ifdef RUN_SHOOT
+		//PMM
+		if (in_flash_number < 0)
+		{
+			VectorCopy (aim, aim_norm);
+			VectorNormalize (aim_norm);
+			angle = DotProduct (aim_norm, forward);
+			//gi.dprintf ("Dot Product:  %f", DotProduct (aim_norm, forward));
+			if (angle < 0.9)  // ~25 degree angle
+			{
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf (" not firing due to bad dotprod %f\n", angle);
+				return;
+			}
+//			else
+//			{
+//				if(g_showlogic && g_showlogic->value)
+//					gi.dprintf (" firing:  dotprod = %f\n", angle);
+//			}
+		}
+		//-PMM
+#endif
+		vectoangles (aim, dir);
+		AngleVectors (dir, forward, right, up);
+		
+		if (skill->value < 2)
+		{
+			r = crandom()*1000;
+			u = crandom()*500;
+		}
+		else
+		{
+			r = crandom()*500;
+			u = crandom()*250;
+		}
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		VectorSubtract (end, start, aim);
+		VectorNormalize (aim);
+	}
+#ifdef CHECK_TARGET
+	if (!(flash_number == 5 || flash_number == 6)) // he's dead
+	{
+		tr = gi.trace (start, NULL, NULL, aim_good, self, MASK_SHOT);
+		if ((tr.ent != self->enemy) && (tr.ent != WORLD))
+		{
+//			if(g_showlogic && g_showlogic->value)
+//				gi.dprintf ("infantry shot aborted due to bad target\n");
+			return;
+		}
+	}
+#endif
+	if (self->s.skinnum <= 1)
+	{
+		monster_fire_blaster (self, start, aim, 5, 600, flash_index, EF_BLASTER);
+	}
+	else if (self->s.skinnum <= 3)
+	{
+		monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index);
+	}
+	else
+	{
+		// PMM - changed to wait from pausetime to not interfere with dodge code
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			self->wait = level.time + (3 + rand() % 8) * FRAMETIME;
+
+		monster_fire_bullet (self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index);
+
+		if (level.time >= self->wait)
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		else
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+	}
+}
+
+// ATTACK1 (blaster/shotgun)
+
+void soldier_fire1 (edict_t *self)
+{
+	soldier_fire (self, 0);
+}
+
+void soldier_attack1_refire1 (edict_t *self)
+{
+	// PMM - blindfire
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		return;
+	}
+	// pmm
+
+	if (!self->enemy)
+		return;
+
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+	else
+		self->monsterinfo.nextframe = FRAME_attak110;
+}
+
+void soldier_attack1_refire2 (edict_t *self)
+{
+	if (!self->enemy)
+		return;
+
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+}
+
+mframe_t soldier_frames_attack1 [] =
+{
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_fire1,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_attack1_refire1,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_cock,
+	ai_charge, 0,  soldier_attack1_refire2,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL
+};
+mmove_t soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run};
+
+// ATTACK2 (blaster/shotgun)
+
+void soldier_fire2 (edict_t *self)
+{
+	soldier_fire (self, 1);
+}
+
+void soldier_attack2_refire1 (edict_t *self)
+{
+	if (!self->enemy)
+		return;
+
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak204;
+	else
+		self->monsterinfo.nextframe = FRAME_attak216;
+}
+
+void soldier_attack2_refire2 (edict_t *self)
+{
+	if (!self->enemy)
+		return;
+
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak204;
+}
+
+mframe_t soldier_frames_attack2 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack2_refire1,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_cock,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack2_refire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run};
+
+// ATTACK3 (duck and shoot)
+/*
+void soldier_duck_down (edict_t *self)
+{
+	if ((g_showlogic) && (g_showlogic->value))
+		gi.dprintf ("duck down - %d!\n", self->s.frame);
+
+	self->monsterinfo.aiflags |= AI_DUCKED;
+//	self->maxs[2] -= 32;
+	self->maxs[2] =  self->monsterinfo.base_height - 32;
+	self->takedamage = DAMAGE_YES;
+	if (self->monsterinfo.duck_wait_time < level.time)
+	{
+		if ((g_showlogic) && (g_showlogic->value))
+			gi.dprintf ("soldier duck with no time!\n");
+		self->monsterinfo.duck_wait_time = level.time + 1;
+	}
+	gi.linkentity (self);
+}
+
+void soldier_duck_up (edict_t *self)
+{
+	if ((g_showlogic) && (g_showlogic->value))
+		gi.dprintf ("duck up - %d!\n", self->s.frame);
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+//	self->maxs[2] += 32;
+	self->maxs[2] = self->monsterinfo.base_height;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+*/
+void soldier_fire3 (edict_t *self)
+{
+	monster_duck_down (self);
+	soldier_fire (self, 2);
+}
+
+void soldier_attack3_refire (edict_t *self)
+{
+	if ((level.time + 0.4) < self->monsterinfo.duck_wait_time)
+		self->monsterinfo.nextframe = FRAME_attak303;
+}
+
+mframe_t soldier_frames_attack3 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire3,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack3_refire,
+	ai_charge, 0, monster_duck_up,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run};
+
+// ATTACK4 (machinegun)
+
+void soldier_fire4 (edict_t *self)
+{
+	soldier_fire (self, 3);
+//
+//	if (self->enemy->health <= 0)
+//		return;
+//
+//	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+//		self->monsterinfo.nextframe = FRAME_attak402;
+}
+
+mframe_t soldier_frames_attack4 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire4,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack4 = {FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run};
+
+/*
+// ATTACK5 (prone)
+
+void soldier_fire5 (edict_t *self)
+{
+	soldier_fire (self, 4);
+}
+
+void soldier_attack5_refire (edict_t *self)
+{
+	if (!self->enemy)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak505;
+}
+
+mframe_t soldier_frames_attack5 [] =
+{
+	ai_charge, 8, NULL,
+	ai_charge, 8, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire5,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack5_refire
+};
+mmove_t soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_run};
+*/
+
+// ATTACK6 (run & shoot)
+
+void soldier_fire8 (edict_t *self)
+{
+	soldier_fire (self, -7);
+//	self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+//	self->monsterinfo.pausetime = level.time + 1000000;
+}
+
+void soldier_attack6_refire (edict_t *self)
+{
+	// PMM - make sure dodge & charge bits are cleared
+	monster_done_dodge (self);
+	soldier_stop_charge (self);
+
+	if (!self->enemy)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+//	if (range(self, self->enemy) < RANGE_MID)
+	if (range(self, self->enemy) < RANGE_NEAR)
+		return;
+
+	if ((skill->value == 3) || ((qrandom() < (0.25*((float)skill->value)))))
+		self->monsterinfo.nextframe = FRAME_runs03;
+}
+
+mframe_t soldier_frames_attack6 [] =
+{
+//	PMM
+//	ai_run, 10, NULL,
+	ai_run, 10, soldier_start_charge,
+	ai_run,  4, NULL,
+	ai_run, 12, soldier_fire8,
+	ai_run, 11, NULL,
+	ai_run, 13, monster_done_dodge,
+	ai_run, 18, NULL,
+	ai_run, 15, NULL,
+	ai_run, 14, NULL,
+	ai_run, 11, NULL,
+	ai_run,  8, NULL,
+	ai_run, 11, NULL,
+	ai_run, 12, NULL,
+	ai_run, 12, NULL,
+	ai_run, 17, soldier_attack6_refire
+};
+mmove_t soldier_move_attack6 = {FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run};
+
+void soldier_attack(edict_t *self)
+{
+	float r, chance;
+
+	monster_done_dodge (self);
+
+	// PMM - blindfire!
+	if (self->monsterinfo.attack_state == AS_BLIND)
+	{
+		// setup shot probabilities
+		if (self->monsterinfo.blind_fire_delay < 1.0)
+			chance = 1.0;
+		else if (self->monsterinfo.blind_fire_delay < 7.5)
+			chance = 0.4;
+		else
+			chance = 0.1;
+
+		r = qrandom();
+
+		// minimum of 2 seconds, plus 0-3, after the shots are done
+		self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + qrandom()*3.0;
+
+		// don't shoot at the origin
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+
+		// don't shoot if the dice say not to
+		if (r > chance)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("blindfire - NO SHOT\n");
+			return;
+		}
+
+		// turn on manual steering to signal both manual steering and blindfire
+		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+		self->monsterinfo.currentmove = &soldier_move_attack1;
+		self->monsterinfo.attack_finished = level.time + 1.5 + qrandom();
+		return;
+	}
+	// pmm
+
+// PMM - added this so the soldiers now run toward you and shoot instead of just stopping and shooting
+//	if ((range(self, self->enemy) >= RANGE_MID) && (r < (skill->value*0.25) && (self->s.skinnum <= 3)))
+
+	r = qrandom();
+
+	if ((!(self->monsterinfo.aiflags & (AI_BLOCKED|AI_STAND_GROUND))) &&
+		(range(self, self->enemy) >= RANGE_NEAR) && 
+		(r < (skill->value*0.25) && 
+		(self->s.skinnum <= 3)))
+	{
+		self->monsterinfo.currentmove = &soldier_move_attack6;
+	}
+	else
+	{
+		if (self->s.skinnum < 4)
+		{
+			if (qrandom() < 0.5)
+				self->monsterinfo.currentmove = &soldier_move_attack1;
+			else
+				self->monsterinfo.currentmove = &soldier_move_attack2;
+		}
+		else
+		{
+			self->monsterinfo.currentmove = &soldier_move_attack4;
+		}
+	}
+}
+
+
+//
+// SIGHT
+//
+
+void soldier_sight(edict_t *self, edict_t *)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+
+//	if ((skill->value > 0) && (self->enemy) && (range(self, self->enemy) >= RANGE_MID))
+	if ((skill->value > 0) && (self->enemy) && (range(self, self->enemy) >= RANGE_NEAR))
+	{
+//	PMM - don't let machinegunners run & shoot
+		if ((qrandom() > 0.75) && (self->s.skinnum <= 3))
+			self->monsterinfo.currentmove = &soldier_move_attack6;
+	}
+}
+
+//
+// DUCK
+//
+/*
+void soldier_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.duck_wait_time)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+*/
+mframe_t soldier_frames_duck [] =
+{
+	ai_move, 5, monster_duck_down,
+	ai_move, -1, monster_duck_hold,
+	ai_move, 1,  NULL,
+	ai_move, 0,  monster_duck_up,
+	ai_move, 5,  NULL
+};
+mmove_t soldier_move_duck = {FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run};
+
+/*
+void soldier_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *tr)
+{
+//===========
+//PMM - rogue rewrite of dodge code.
+// lots o' changes in here.  Basically, they now check the tr and see if ducking would help,
+// and if it doesn't, they dodge like mad
+	float	r = qrandom();
+	float	height;
+
+	if ((g_showlogic) && (g_showlogic->value))
+	{
+		if (self->monsterinfo.aiflags & AI_DODGING)
+			gi.dprintf ("dodging - ");
+		if (self->monsterinfo.aiflags & AI_DUCKED)
+			gi.dprintf ("ducked - ");
+	}
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget (self);
+	}
+
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+	{
+		if ((g_showlogic) && (g_showlogic->value))
+			gi.dprintf ("timeout\n");
+		return;
+	}
+
+	// skill level determination..
+	if (r > (0.25*((skill->value)+1)))
+	{
+		if ((g_showlogic) && (g_showlogic->value))
+			gi.dprintf ("skillout\n");
+		return;
+	}
+
+	// stop charging, since we're going to dodge (somehow) instead
+	soldier_stop_charge (self);
+
+	height = self->absmax[2]-32-1;  // the -1 is because the absmax is s.origin + maxs + 1
+
+	// if we're ducking already, or the shot is at our knees
+	if ((tr->endpos[2] <= height) || (self->monsterinfo.aiflags & AI_DUCKED))
+	{
+		vec3_t right, diff;
+
+		// if we're already dodging, just finish the sequence, i.e. don't do anything else
+		if (self->monsterinfo.aiflags & AI_DODGING)
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf ("already dodging\n");
+			return;
+		}
+
+		AngleVectors (self->s.angles, NULL, right, NULL);
+		VectorSubtract (tr->endpos, self->s.origin, diff);
+
+		if (DotProduct (right, diff) < 0)
+		{
+			self->monsterinfo.lefty = 1;
+//			gi.dprintf ("left\n");
+		} else {
+//			gi.dprintf ("right\n");
+		}
+		// if it doesn't sense to duck, try to strafe and shoot
+		// we don't want the machine gun guys running & shooting (looks bad)
+
+		// if we are currently ducked, unduck
+		if (self->monsterinfo.aiflags & AI_DUCKED)
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf ("unducking - ");
+			soldier_duck_up(self);
+		}
+
+		self->monsterinfo.aiflags |= AI_DODGING;
+		self->monsterinfo.attack_state = AS_SLIDING;
+
+		if (self->s.skinnum <= 3)
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf ("shooting back!\n");
+			self->monsterinfo.currentmove = &soldier_move_attack6;
+		}
+		else
+		{
+			if ((g_showlogic) && (g_showlogic->value))
+				gi.dprintf ("strafing away!\n");
+			self->monsterinfo.currentmove = &soldier_move_start_run;
+		}
+		return;
+	}
+
+	// if we're here, we're ducking, so clear the dodge bit if it's set
+
+	if ((g_showlogic) && (g_showlogic->value))
+		gi.dprintf ("ducking!\n");
+	if (skill->value == 0)
+	{
+		// set this prematurely; it doesn't hurt, and prevents extra iterations
+		self->monsterinfo.aiflags |= AI_DUCKED;
+		monster_done_dodge (self);
+		self->monsterinfo.currentmove = &soldier_move_duck;
+		// PMM - stupid dodge
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		return;
+	}
+// PMM - since we're only ducking some of the time, this needs to be moved down below
+//	self->monsterinfo.duck_wait_time = level.time + eta + 0.3;
+
+	r = qrandom();
+
+	// set this prematurely; it doesn't hurt, and prevents extra iterations
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	monster_done_dodge (self);
+
+	if (r > (skill->value * 0.33))
+	{
+		self->monsterinfo.currentmove = &soldier_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+		// has to be done immediately otherwise he can get stuck
+		soldier_duck_down(self);
+	}
+	else
+	{
+		// has to be done immediately otherwise he can get stuck
+		soldier_duck_down(self);
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		self->monsterinfo.currentmove = &soldier_move_attack3;
+		self->monsterinfo.nextframe = FRAME_attak301;
+	}
+	return;
+//PMM
+//===========
+
+}
+*/
+// pmm - blocking code
+
+qboolean soldier_blocked (edict_t *self, float dist)
+{
+	// don't do anything if you're dodging
+	if ((self->monsterinfo.aiflags & AI_DODGING) || (self->monsterinfo.aiflags & AI_DUCKED))
+		return false;
+
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+//	if(blocked_checkjump (self, dist, 192, 40))
+//	{
+//		soldier_jump(self);
+//		return true;
+//	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+
+//
+// DEATH
+//
+
+void soldier_fire6 (edict_t *self)
+{
+	soldier_fire (self, 5);
+}
+
+void soldier_fire7 (edict_t *self)
+{
+	soldier_fire (self, 6);
+}
+
+void soldier_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+// pmm - this quickie does a location trace to try to grow the bounding box
+//
+// this is because the frames are off; the origin is at the guy's feet.
+void soldier_dead2 (edict_t *self)
+{
+	vec3_t	tempmins, tempmaxs, temporg;
+	trace_t	tr;
+
+	VectorCopy (self->s.origin, temporg);
+	// this is because location traces done at the floor are guaranteed to hit the floor
+	// (inside the sv_trace code it grows the bbox by 1 in all directions)
+	temporg[2] += 1;
+
+	VectorSet (tempmins, -32, -32, -24);
+	VectorSet (tempmaxs, 32, 32, -8);
+
+	tr = gi.trace (temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID);
+	if (tr.startsolid || tr.allsolid)
+	{
+		VectorSet (self->mins, -16, -16, -24);
+		VectorSet (self->maxs, 16, 16, -8);
+	}
+	else
+	{
+		VectorCopy (tempmins, self->mins);
+		VectorCopy (tempmaxs, self->maxs);
+	}
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t soldier_frames_death1 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldier_fire6,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldier_fire7,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death1 = {FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead};
+
+mframe_t soldier_frames_death2 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death2 = {FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead};
+
+mframe_t soldier_frames_death3 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+};
+mmove_t soldier_move_death3 = {FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead};
+
+mframe_t soldier_frames_death4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+// PMM -changed to soldier_dead2 to get a larger bounding box
+mmove_t soldier_move_death4 = {FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead2};
+
+mframe_t soldier_frames_death5 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death5 = {FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead};
+
+mframe_t soldier_frames_death6 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death6 = {FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead};
+
+void soldier_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t point)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 3; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum |= 1;
+
+	if (self->s.skinnum == 1)
+		gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+	else if (self->s.skinnum == 3)
+		gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	else // (self->s.skinnum == 5)
+		gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+
+	if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+	{
+		// head shot
+		self->monsterinfo.currentmove = &soldier_move_death3;
+		return;
+	}
+
+	n = rand() % 5;
+	if (n == 0)
+		self->monsterinfo.currentmove = &soldier_move_death1;
+	else if (n == 1)
+		self->monsterinfo.currentmove = &soldier_move_death2;
+	else if (n == 2)
+		self->monsterinfo.currentmove = &soldier_move_death4;
+	else if (n == 3)
+		self->monsterinfo.currentmove = &soldier_move_death5;
+	else
+		self->monsterinfo.currentmove = &soldier_move_death6;
+}
+
+//
+// NEW DODGE CODE
+//
+
+void soldier_sidestep (edict_t *self)
+{
+	if (self->s.skinnum <= 3)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("shooting back!\n");
+		if (self->monsterinfo.currentmove != &soldier_move_attack6)
+			self->monsterinfo.currentmove = &soldier_move_attack6;
+	}
+	else
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("strafing away!\n");
+		if (self->monsterinfo.currentmove != &soldier_move_start_run)
+			self->monsterinfo.currentmove = &soldier_move_start_run;
+	}
+}
+
+void soldier_duck (edict_t *self, float eta)
+{
+	float r;
+
+	// has to be done immediately otherwise he can get stuck
+	monster_duck_down(self);
+
+	if (skill->value == 0)
+	{
+		// PMM - stupid dodge
+		self->monsterinfo.nextframe = FRAME_duck01;
+		self->monsterinfo.currentmove = &soldier_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+		return;
+	}
+
+	r = qrandom();
+
+	if (r > (skill->value * 0.3))
+	{
+		self->monsterinfo.nextframe = FRAME_duck01;
+		self->monsterinfo.currentmove = &soldier_move_duck;
+		self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value));
+	}
+	else
+	{
+		self->monsterinfo.nextframe = FRAME_attak301;
+		self->monsterinfo.currentmove = &soldier_move_attack3;
+		self->monsterinfo.duck_wait_time = level.time + eta + 1;
+	}
+	return;
+}
+
+//=========
+//ROGUE
+void soldier_blind (edict_t *self);
+
+mframe_t soldier_frames_blind [] =
+{
+	ai_move, 0, soldier_idle,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t soldier_move_blind = {FRAME_stand101, FRAME_stand130, soldier_frames_blind, soldier_blind};
+
+void soldier_blind (edict_t *self)
+{
+	self->monsterinfo.currentmove = &soldier_move_blind;
+}
+//ROGUE
+//=========
+
+//
+// SPAWN
+//
+
+void SP_monster_soldier_x (edict_t *self)
+{
+
+	self->s.modelindex = gi.modelindex ("models/monsters/soldier/tris.md2");
+	//PMM
+//	self->s.effects |= EF_SPLATTER;
+	//PMM
+	self->monsterinfo.scale = MODEL_SCALE;
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	sound_idle =	gi.soundindex ("soldier/solidle1.wav");
+	sound_sight1 =	gi.soundindex ("soldier/solsght1.wav");
+	sound_sight2 =	gi.soundindex ("soldier/solsrch1.wav");
+	sound_cock =	gi.soundindex ("infantry/infatck3.wav");
+
+	self->mass = 100;
+
+	self->pain = soldier_pain;
+	self->die = soldier_die;
+
+	self->monsterinfo.stand = soldier_stand;
+	self->monsterinfo.walk = soldier_walk;
+	self->monsterinfo.run = soldier_run;
+	self->monsterinfo.dodge = M_MonsterDodge;
+	self->monsterinfo.attack = soldier_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = soldier_sight;
+
+//=====
+//ROGUE
+	self->monsterinfo.blocked = soldier_blocked;
+	self->monsterinfo.duck = soldier_duck;
+	self->monsterinfo.unduck = monster_duck_up;
+	self->monsterinfo.sidestep = soldier_sidestep;
+
+	if(self->spawnflags & 8)	// blind
+		self->monsterinfo.stand = soldier_blind;
+//ROGUE
+//=====
+
+	gi.linkentity (self);
+
+	self->monsterinfo.stand (self);
+
+	walkmonster_start (self);
+}
+
+
+/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+
+Blind - monster will just stand there until triggered
+*/
+void SP_monster_soldier_light (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain_light = gi.soundindex ("soldier/solpain2.wav");
+	sound_death_light =	gi.soundindex ("soldier/soldeth2.wav");
+	gi.modelindex ("models/objects/laser/tris.md2");
+	gi.soundindex ("misc/lasfly.wav");
+	gi.soundindex ("soldier/solatck2.wav");
+
+	self->s.skinnum = 0;
+	self->health = 20;
+	self->gib_health = -30;
+
+	// PMM - blindfire
+	self->monsterinfo.blindfire = true;
+}
+
+/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+
+Blind - monster will just stand there until triggered
+*/
+void SP_monster_soldier (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain = gi.soundindex ("soldier/solpain1.wav");
+	sound_death = gi.soundindex ("soldier/soldeth1.wav");
+	gi.soundindex ("soldier/solatck1.wav");
+
+	self->s.skinnum = 2;
+	self->health = 30;
+	self->gib_health = -30;
+}
+
+/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind
+
+Blind - monster will just stand there until triggered
+*/
+void SP_monster_soldier_ss (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain_ss = gi.soundindex ("soldier/solpain3.wav");
+	sound_death_ss = gi.soundindex ("soldier/soldeth3.wav");
+	gi.soundindex ("soldier/solatck3.wav");
+
+	self->s.skinnum = 4;
+	self->health = 40;
+	self->gib_health = -30;
+}
--- /dev/null
+++ b/rogue/m_soldier.h
@@ -1,0 +1,481 @@
+// G:\quake2\baseq2\models/monsters/soldier
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak201        	12
+#define FRAME_attak202        	13
+#define FRAME_attak203        	14
+#define FRAME_attak204        	15
+#define FRAME_attak205        	16
+#define FRAME_attak206        	17
+#define FRAME_attak207        	18
+#define FRAME_attak208        	19
+#define FRAME_attak209        	20
+#define FRAME_attak210        	21
+#define FRAME_attak211        	22
+#define FRAME_attak212        	23
+#define FRAME_attak213        	24
+#define FRAME_attak214        	25
+#define FRAME_attak215        	26
+#define FRAME_attak216        	27
+#define FRAME_attak217        	28
+#define FRAME_attak218        	29
+#define FRAME_attak301        	30
+#define FRAME_attak302        	31
+#define FRAME_attak303        	32
+#define FRAME_attak304        	33
+#define FRAME_attak305        	34
+#define FRAME_attak306        	35
+#define FRAME_attak307        	36
+#define FRAME_attak308        	37
+#define FRAME_attak309        	38
+#define FRAME_attak401        	39
+#define FRAME_attak402        	40
+#define FRAME_attak403        	41
+#define FRAME_attak404        	42
+#define FRAME_attak405        	43
+#define FRAME_attak406        	44
+#define FRAME_duck01          	45
+#define FRAME_duck02          	46
+#define FRAME_duck03          	47
+#define FRAME_duck04          	48
+#define FRAME_duck05          	49
+#define FRAME_pain101         	50
+#define FRAME_pain102         	51
+#define FRAME_pain103         	52
+#define FRAME_pain104         	53
+#define FRAME_pain105         	54
+#define FRAME_pain201         	55
+#define FRAME_pain202         	56
+#define FRAME_pain203         	57
+#define FRAME_pain204         	58
+#define FRAME_pain205         	59
+#define FRAME_pain206         	60
+#define FRAME_pain207         	61
+#define FRAME_pain301         	62
+#define FRAME_pain302         	63
+#define FRAME_pain303         	64
+#define FRAME_pain304         	65
+#define FRAME_pain305         	66
+#define FRAME_pain306         	67
+#define FRAME_pain307         	68
+#define FRAME_pain308         	69
+#define FRAME_pain309         	70
+#define FRAME_pain310         	71
+#define FRAME_pain311         	72
+#define FRAME_pain312         	73
+#define FRAME_pain313         	74
+#define FRAME_pain314         	75
+#define FRAME_pain315         	76
+#define FRAME_pain316         	77
+#define FRAME_pain317         	78
+#define FRAME_pain318         	79
+#define FRAME_pain401         	80
+#define FRAME_pain402         	81
+#define FRAME_pain403         	82
+#define FRAME_pain404         	83
+#define FRAME_pain405         	84
+#define FRAME_pain406         	85
+#define FRAME_pain407         	86
+#define FRAME_pain408         	87
+#define FRAME_pain409         	88
+#define FRAME_pain410         	89
+#define FRAME_pain411         	90
+#define FRAME_pain412         	91
+#define FRAME_pain413         	92
+#define FRAME_pain414         	93
+#define FRAME_pain415         	94
+#define FRAME_pain416         	95
+#define FRAME_pain417         	96
+#define FRAME_run01           	97
+#define FRAME_run02           	98
+#define FRAME_run03           	99
+#define FRAME_run04           	100
+#define FRAME_run05           	101
+#define FRAME_run06           	102
+#define FRAME_run07           	103
+#define FRAME_run08           	104
+#define FRAME_run09           	105
+#define FRAME_run10           	106
+#define FRAME_run11           	107
+#define FRAME_run12           	108
+#define FRAME_runs01          	109
+#define FRAME_runs02          	110
+#define FRAME_runs03          	111
+#define FRAME_runs04          	112
+#define FRAME_runs05          	113
+#define FRAME_runs06          	114
+#define FRAME_runs07          	115
+#define FRAME_runs08          	116
+#define FRAME_runs09          	117
+#define FRAME_runs10          	118
+#define FRAME_runs11          	119
+#define FRAME_runs12          	120
+#define FRAME_runs13          	121
+#define FRAME_runs14          	122
+#define FRAME_runs15          	123
+#define FRAME_runs16          	124
+#define FRAME_runs17          	125
+#define FRAME_runs18          	126
+#define FRAME_runt01          	127
+#define FRAME_runt02          	128
+#define FRAME_runt03          	129
+#define FRAME_runt04          	130
+#define FRAME_runt05          	131
+#define FRAME_runt06          	132
+#define FRAME_runt07          	133
+#define FRAME_runt08          	134
+#define FRAME_runt09          	135
+#define FRAME_runt10          	136
+#define FRAME_runt11          	137
+#define FRAME_runt12          	138
+#define FRAME_runt13          	139
+#define FRAME_runt14          	140
+#define FRAME_runt15          	141
+#define FRAME_runt16          	142
+#define FRAME_runt17          	143
+#define FRAME_runt18          	144
+#define FRAME_runt19          	145
+#define FRAME_stand101        	146
+#define FRAME_stand102        	147
+#define FRAME_stand103        	148
+#define FRAME_stand104        	149
+#define FRAME_stand105        	150
+#define FRAME_stand106        	151
+#define FRAME_stand107        	152
+#define FRAME_stand108        	153
+#define FRAME_stand109        	154
+#define FRAME_stand110        	155
+#define FRAME_stand111        	156
+#define FRAME_stand112        	157
+#define FRAME_stand113        	158
+#define FRAME_stand114        	159
+#define FRAME_stand115        	160
+#define FRAME_stand116        	161
+#define FRAME_stand117        	162
+#define FRAME_stand118        	163
+#define FRAME_stand119        	164
+#define FRAME_stand120        	165
+#define FRAME_stand121        	166
+#define FRAME_stand122        	167
+#define FRAME_stand123        	168
+#define FRAME_stand124        	169
+#define FRAME_stand125        	170
+#define FRAME_stand126        	171
+#define FRAME_stand127        	172
+#define FRAME_stand128        	173
+#define FRAME_stand129        	174
+#define FRAME_stand130        	175
+#define FRAME_stand301        	176
+#define FRAME_stand302        	177
+#define FRAME_stand303        	178
+#define FRAME_stand304        	179
+#define FRAME_stand305        	180
+#define FRAME_stand306        	181
+#define FRAME_stand307        	182
+#define FRAME_stand308        	183
+#define FRAME_stand309        	184
+#define FRAME_stand310        	185
+#define FRAME_stand311        	186
+#define FRAME_stand312        	187
+#define FRAME_stand313        	188
+#define FRAME_stand314        	189
+#define FRAME_stand315        	190
+#define FRAME_stand316        	191
+#define FRAME_stand317        	192
+#define FRAME_stand318        	193
+#define FRAME_stand319        	194
+#define FRAME_stand320        	195
+#define FRAME_stand321        	196
+#define FRAME_stand322        	197
+#define FRAME_stand323        	198
+#define FRAME_stand324        	199
+#define FRAME_stand325        	200
+#define FRAME_stand326        	201
+#define FRAME_stand327        	202
+#define FRAME_stand328        	203
+#define FRAME_stand329        	204
+#define FRAME_stand330        	205
+#define FRAME_stand331        	206
+#define FRAME_stand332        	207
+#define FRAME_stand333        	208
+#define FRAME_stand334        	209
+#define FRAME_stand335        	210
+#define FRAME_stand336        	211
+#define FRAME_stand337        	212
+#define FRAME_stand338        	213
+#define FRAME_stand339        	214
+#define FRAME_walk101         	215
+#define FRAME_walk102         	216
+#define FRAME_walk103         	217
+#define FRAME_walk104         	218
+#define FRAME_walk105         	219
+#define FRAME_walk106         	220
+#define FRAME_walk107         	221
+#define FRAME_walk108         	222
+#define FRAME_walk109         	223
+#define FRAME_walk110         	224
+#define FRAME_walk111         	225
+#define FRAME_walk112         	226
+#define FRAME_walk113         	227
+#define FRAME_walk114         	228
+#define FRAME_walk115         	229
+#define FRAME_walk116         	230
+#define FRAME_walk117         	231
+#define FRAME_walk118         	232
+#define FRAME_walk119         	233
+#define FRAME_walk120         	234
+#define FRAME_walk121         	235
+#define FRAME_walk122         	236
+#define FRAME_walk123         	237
+#define FRAME_walk124         	238
+#define FRAME_walk125         	239
+#define FRAME_walk126         	240
+#define FRAME_walk127         	241
+#define FRAME_walk128         	242
+#define FRAME_walk129         	243
+#define FRAME_walk130         	244
+#define FRAME_walk131         	245
+#define FRAME_walk132         	246
+#define FRAME_walk133         	247
+#define FRAME_walk201         	248
+#define FRAME_walk202         	249
+#define FRAME_walk203         	250
+#define FRAME_walk204         	251
+#define FRAME_walk205         	252
+#define FRAME_walk206         	253
+#define FRAME_walk207         	254
+#define FRAME_walk208         	255
+#define FRAME_walk209         	256
+#define FRAME_walk210         	257
+#define FRAME_walk211         	258
+#define FRAME_walk212         	259
+#define FRAME_walk213         	260
+#define FRAME_walk214         	261
+#define FRAME_walk215         	262
+#define FRAME_walk216         	263
+#define FRAME_walk217         	264
+#define FRAME_walk218         	265
+#define FRAME_walk219         	266
+#define FRAME_walk220         	267
+#define FRAME_walk221         	268
+#define FRAME_walk222         	269
+#define FRAME_walk223         	270
+#define FRAME_walk224         	271
+#define FRAME_death101        	272
+#define FRAME_death102        	273
+#define FRAME_death103        	274
+#define FRAME_death104        	275
+#define FRAME_death105        	276
+#define FRAME_death106        	277
+#define FRAME_death107        	278
+#define FRAME_death108        	279
+#define FRAME_death109        	280
+#define FRAME_death110        	281
+#define FRAME_death111        	282
+#define FRAME_death112        	283
+#define FRAME_death113        	284
+#define FRAME_death114        	285
+#define FRAME_death115        	286
+#define FRAME_death116        	287
+#define FRAME_death117        	288
+#define FRAME_death118        	289
+#define FRAME_death119        	290
+#define FRAME_death120        	291
+#define FRAME_death121        	292
+#define FRAME_death122        	293
+#define FRAME_death123        	294
+#define FRAME_death124        	295
+#define FRAME_death125        	296
+#define FRAME_death126        	297
+#define FRAME_death127        	298
+#define FRAME_death128        	299
+#define FRAME_death129        	300
+#define FRAME_death130        	301
+#define FRAME_death131        	302
+#define FRAME_death132        	303
+#define FRAME_death133        	304
+#define FRAME_death134        	305
+#define FRAME_death135        	306
+#define FRAME_death136        	307
+#define FRAME_death201        	308
+#define FRAME_death202        	309
+#define FRAME_death203        	310
+#define FRAME_death204        	311
+#define FRAME_death205        	312
+#define FRAME_death206        	313
+#define FRAME_death207        	314
+#define FRAME_death208        	315
+#define FRAME_death209        	316
+#define FRAME_death210        	317
+#define FRAME_death211        	318
+#define FRAME_death212        	319
+#define FRAME_death213        	320
+#define FRAME_death214        	321
+#define FRAME_death215        	322
+#define FRAME_death216        	323
+#define FRAME_death217        	324
+#define FRAME_death218        	325
+#define FRAME_death219        	326
+#define FRAME_death220        	327
+#define FRAME_death221        	328
+#define FRAME_death222        	329
+#define FRAME_death223        	330
+#define FRAME_death224        	331
+#define FRAME_death225        	332
+#define FRAME_death226        	333
+#define FRAME_death227        	334
+#define FRAME_death228        	335
+#define FRAME_death229        	336
+#define FRAME_death230        	337
+#define FRAME_death231        	338
+#define FRAME_death232        	339
+#define FRAME_death233        	340
+#define FRAME_death234        	341
+#define FRAME_death235        	342
+#define FRAME_death301        	343
+#define FRAME_death302        	344
+#define FRAME_death303        	345
+#define FRAME_death304        	346
+#define FRAME_death305        	347
+#define FRAME_death306        	348
+#define FRAME_death307        	349
+#define FRAME_death308        	350
+#define FRAME_death309        	351
+#define FRAME_death310        	352
+#define FRAME_death311        	353
+#define FRAME_death312        	354
+#define FRAME_death313        	355
+#define FRAME_death314        	356
+#define FRAME_death315        	357
+#define FRAME_death316        	358
+#define FRAME_death317        	359
+#define FRAME_death318        	360
+#define FRAME_death319        	361
+#define FRAME_death320        	362
+#define FRAME_death321        	363
+#define FRAME_death322        	364
+#define FRAME_death323        	365
+#define FRAME_death324        	366
+#define FRAME_death325        	367
+#define FRAME_death326        	368
+#define FRAME_death327        	369
+#define FRAME_death328        	370
+#define FRAME_death329        	371
+#define FRAME_death330        	372
+#define FRAME_death331        	373
+#define FRAME_death332        	374
+#define FRAME_death333        	375
+#define FRAME_death334        	376
+#define FRAME_death335        	377
+#define FRAME_death336        	378
+#define FRAME_death337        	379
+#define FRAME_death338        	380
+#define FRAME_death339        	381
+#define FRAME_death340        	382
+#define FRAME_death341        	383
+#define FRAME_death342        	384
+#define FRAME_death343        	385
+#define FRAME_death344        	386
+#define FRAME_death345        	387
+#define FRAME_death401        	388
+#define FRAME_death402        	389
+#define FRAME_death403        	390
+#define FRAME_death404        	391
+#define FRAME_death405        	392
+#define FRAME_death406        	393
+#define FRAME_death407        	394
+#define FRAME_death408        	395
+#define FRAME_death409        	396
+#define FRAME_death410        	397
+#define FRAME_death411        	398
+#define FRAME_death412        	399
+#define FRAME_death413        	400
+#define FRAME_death414        	401
+#define FRAME_death415        	402
+#define FRAME_death416        	403
+#define FRAME_death417        	404
+#define FRAME_death418        	405
+#define FRAME_death419        	406
+#define FRAME_death420        	407
+#define FRAME_death421        	408
+#define FRAME_death422        	409
+#define FRAME_death423        	410
+#define FRAME_death424        	411
+#define FRAME_death425        	412
+#define FRAME_death426        	413
+#define FRAME_death427        	414
+#define FRAME_death428        	415
+#define FRAME_death429        	416
+#define FRAME_death430        	417
+#define FRAME_death431        	418
+#define FRAME_death432        	419
+#define FRAME_death433        	420
+#define FRAME_death434        	421
+#define FRAME_death435        	422
+#define FRAME_death436        	423
+#define FRAME_death437        	424
+#define FRAME_death438        	425
+#define FRAME_death439        	426
+#define FRAME_death440        	427
+#define FRAME_death441        	428
+#define FRAME_death442        	429
+#define FRAME_death443        	430
+#define FRAME_death444        	431
+#define FRAME_death445        	432
+#define FRAME_death446        	433
+#define FRAME_death447        	434
+#define FRAME_death448        	435
+#define FRAME_death449        	436
+#define FRAME_death450        	437
+#define FRAME_death451        	438
+#define FRAME_death452        	439
+#define FRAME_death453        	440
+#define FRAME_death501        	441
+#define FRAME_death502        	442
+#define FRAME_death503        	443
+#define FRAME_death504        	444
+#define FRAME_death505        	445
+#define FRAME_death506        	446
+#define FRAME_death507        	447
+#define FRAME_death508        	448
+#define FRAME_death509        	449
+#define FRAME_death510        	450
+#define FRAME_death511        	451
+#define FRAME_death512        	452
+#define FRAME_death513        	453
+#define FRAME_death514        	454
+#define FRAME_death515        	455
+#define FRAME_death516        	456
+#define FRAME_death517        	457
+#define FRAME_death518        	458
+#define FRAME_death519        	459
+#define FRAME_death520        	460
+#define FRAME_death521        	461
+#define FRAME_death522        	462
+#define FRAME_death523        	463
+#define FRAME_death524        	464
+#define FRAME_death601        	465
+#define FRAME_death602        	466
+#define FRAME_death603        	467
+#define FRAME_death604        	468
+#define FRAME_death605        	469
+#define FRAME_death606        	470
+#define FRAME_death607        	471
+#define FRAME_death608        	472
+#define FRAME_death609        	473
+#define FRAME_death610        	474
+
+#define MODEL_SCALE		1.200000
--- /dev/null
+++ b/rogue/m_stalker.c
@@ -1,0 +1,1209 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_stalker.h"
+
+static int	sound_pain;
+static int	sound_die;
+static int	sound_sight;
+static int  sound_punch_hit1;
+static int  sound_punch_hit2;
+static int	sound_idle;
+
+int stalker_do_pounce(edict_t *self, vec3_t dest);
+void stalker_stand (edict_t *self);
+void stalker_run (edict_t *self);
+void stalker_walk (edict_t *self);
+void stalker_jump (edict_t *self);
+void stalker_dodge_jump (edict_t *self);
+void stalker_swing_check_l (edict_t *self);
+void stalker_swing_check_r (edict_t *self);
+void stalker_swing_attack (edict_t *self);
+void stalker_jump_straightup (edict_t *self);
+void stalker_jump_wait_land (edict_t *self);
+void stalker_false_death (edict_t *self);
+void stalker_false_death_start (edict_t *self);
+qboolean stalker_ok_to_transition (edict_t *self);
+
+#define STALKER_ON_CEILING(ent)  ( ent->gravityVector[2] > 0 ? 1 : 0 )
+
+//extern qboolean SV_StepDirection (edict_t *ent, float yaw, float dist);
+extern qboolean SV_PointCloseEnough (edict_t *ent, vec3_t goal, float dist);
+extern void drawbbox(edict_t *self);
+
+//=========================
+//=========================
+qboolean stalker_ok_to_transition (edict_t *self)
+{
+	trace_t		trace;
+	vec3_t		pt, start;
+	float		max_dist;
+	float		margin;
+	float		end_height;
+
+	if(STALKER_ON_CEILING(self))
+	{
+		max_dist = -384;
+		margin = self->mins[2] - 8;
+	}	
+	else
+	{
+		// her stalkers are just better
+		if (self->monsterinfo.aiflags & AI_SPAWNED_WIDOW)
+			max_dist = 256;
+		else
+			max_dist = 180;
+		margin = self->maxs[2] + 8;
+	}
+
+	VectorCopy(self->s.origin, pt);
+	pt[2] += max_dist;
+	trace = gi.trace (self->s.origin, self->mins, self->maxs, pt, self, MASK_MONSTERSOLID);
+
+	if(trace.fraction == 1.0 || 
+	   !(trace.contents & CONTENTS_SOLID) ||
+	   (trace.ent != WORLD))
+	{
+		if(STALKER_ON_CEILING(self))
+		{
+			if(trace.plane.normal[2] < 0.9)
+				return false;
+		}
+		else
+		{
+			if(trace.plane.normal[2] > -0.9)
+				return false;
+		}
+	}
+//	gi.dprintf("stalker_check_pt: main check ok\n");
+
+	end_height = trace.endpos[2];
+
+	// check the four corners, tracing only to the endpoint of the center trace (vertically).
+	pt[0] = self->absmin[0];
+	pt[1] = self->absmin[1];
+	pt[2] = trace.endpos[2] + margin;	// give a little margin of error to allow slight inclines
+	VectorCopy(pt, start);
+	start[2] = self->s.origin[2];
+	trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+	if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != WORLD))
+	{
+//		gi.dprintf("stalker_check_pt: absmin/absmin failed\n");
+		return false;
+	}
+	if(abs(end_height + margin - trace.endpos[2]) > 8)
+		return false;
+
+	pt[0] = self->absmax[0];
+	pt[1] = self->absmin[1];
+	VectorCopy(pt, start);
+	start[2] = self->s.origin[2];
+	trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+	if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != WORLD))
+	{
+//		gi.dprintf("stalker_check_pt: absmax/absmin failed\n");
+		return false;
+	}
+	if(abs(end_height + margin - trace.endpos[2]) > 8)
+		return false;
+
+	pt[0] = self->absmax[0];
+	pt[1] = self->absmax[1];
+	VectorCopy(pt, start);
+	start[2] = self->s.origin[2];
+	trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+	if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != WORLD))
+	{
+//		gi.dprintf("stalker_check_pt: absmax/absmax failed\n");
+		return false;
+	}
+	if(abs(end_height + margin - trace.endpos[2]) > 8)
+		return false;
+
+	pt[0] = self->absmin[0];
+	pt[1] = self->absmax[1];
+	VectorCopy(pt, start);
+	start[2] = self->s.origin[2];
+	trace = gi.trace( start, vec3_origin, vec3_origin, pt, self, MASK_MONSTERSOLID);
+	if(trace.fraction == 1.0 || !(trace.contents & CONTENTS_SOLID) || (trace.ent != WORLD))
+	{
+//		gi.dprintf("stalker_check_pt: absmin/absmax failed\n");
+		return false;
+	}
+	if(abs(end_height + margin - trace.endpos[2]) > 8)
+		return false;
+
+	return true;
+}
+
+//=========================
+//=========================
+void stalker_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+// ******************
+// IDLE
+// ******************
+
+void stalker_idle_noise (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_idle, 0.5, ATTN_IDLE, 0);
+}
+
+mframe_t stalker_frames_idle [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, stalker_idle_noise,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL
+};
+mmove_t stalker_move_idle = {FRAME_idle01, FRAME_idle21, stalker_frames_idle, stalker_stand};
+
+mframe_t stalker_frames_idle2 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t stalker_move_idle2 = {FRAME_idle201, FRAME_idle213, stalker_frames_idle2, stalker_stand};
+
+void stalker_idle (edict_t *self)
+{ 
+	if (qrandom() < 0.35)
+		self->monsterinfo.currentmove = &stalker_move_idle;
+	else
+		self->monsterinfo.currentmove = &stalker_move_idle2;
+}
+
+// ******************
+// STAND
+// ******************
+
+mframe_t stalker_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, stalker_idle_noise,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL
+};
+mmove_t	stalker_move_stand = {FRAME_idle01, FRAME_idle21, stalker_frames_stand, stalker_stand};
+
+void stalker_stand (edict_t *self)
+{
+	if (qrandom() < 0.25)
+		self->monsterinfo.currentmove = &stalker_move_stand;
+	else
+		self->monsterinfo.currentmove = &stalker_move_idle2;
+}
+
+// ******************
+// RUN
+// ******************
+
+mframe_t stalker_frames_run [] =
+{
+	ai_run, 13, NULL,
+	ai_run, 17, NULL,
+	ai_run, 21, NULL,
+	ai_run, 18, NULL
+
+/*	ai_run, 15, NULL,
+	ai_run, 20, NULL,
+	ai_run, 18, NULL,
+	ai_run, 14, NULL*/
+};
+mmove_t stalker_move_run = {FRAME_run01, FRAME_run04, stalker_frames_run, NULL};
+
+void stalker_run (edict_t *self)
+{
+//	gi.dprintf("stalker_run %5.1f\n", level.time);
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &stalker_move_stand;
+	else
+		self->monsterinfo.currentmove = &stalker_move_run;
+}
+
+// ******************
+// WALK
+// ******************
+
+mframe_t stalker_frames_walk [] =
+{
+	ai_walk, 4, NULL,
+	ai_walk, 6, NULL,
+	ai_walk, 8, NULL,
+	ai_walk, 5, NULL,
+
+	ai_walk, 4, NULL,
+	ai_walk, 6, NULL,
+	ai_walk, 8, NULL,
+	ai_walk, 4, NULL
+};
+mmove_t stalker_move_walk = {FRAME_walk01, FRAME_walk08, stalker_frames_walk, stalker_walk};
+
+void stalker_walk (edict_t *self)
+{
+//	gi.dprintf("stalker_walk\n");
+	self->monsterinfo.currentmove = &stalker_move_walk;
+}
+
+// ******************
+// false death
+// ******************
+mframe_t stalker_frames_reactivate [] = 
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t stalker_move_false_death_end = { FRAME_reactive01, FRAME_reactive04, stalker_frames_reactivate, stalker_run };
+
+void stalker_reactivate (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_STAND_GROUND;
+	self->monsterinfo.currentmove = &stalker_move_false_death_end;
+}
+
+void stalker_heal (edict_t *self)
+{
+	if(skill->value == 2)
+		self->health+=2;
+	else if(skill->value == 3)
+		self->health+=3;
+	else
+		self->health++;
+
+//	gi.dprintf("stalker_heal: %d\n", self->health);
+
+	if(self->health > (self->max_health/2))
+		self->s.skinnum = 0;
+
+	if(self->health >= self->max_health)
+	{
+		self->health = self->max_health;
+		stalker_reactivate(self);
+	}
+}
+
+mframe_t stalker_frames_false_death [] =
+{
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal,
+	ai_move, 0, stalker_heal
+};
+mmove_t stalker_move_false_death = {FRAME_twitch01, FRAME_twitch10, stalker_frames_false_death, stalker_false_death};
+
+void stalker_false_death (edict_t *self)
+{
+	self->monsterinfo.currentmove = &stalker_move_false_death;
+}
+
+mframe_t stalker_frames_false_death_start [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+};
+mmove_t stalker_move_false_death_start = {FRAME_death01, FRAME_death09, stalker_frames_false_death_start, stalker_false_death};
+
+void stalker_false_death_start (edict_t *self)
+{
+	self->s.angles[2] = 0;
+	VectorSet(self->gravityVector, 0, 0, -1);
+
+	self->monsterinfo.aiflags |= AI_STAND_GROUND;
+	self->monsterinfo.currentmove = &stalker_move_false_death_start;
+}
+
+
+// ******************
+// PAIN
+// ******************
+
+mframe_t stalker_frames_pain [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0, NULL
+};
+mmove_t stalker_move_pain = {FRAME_pain01, FRAME_pain04, stalker_frames_pain, stalker_run};
+
+void stalker_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	if (self->health < (self->max_health / 2)) 
+	{
+		self->s.skinnum = 1;
+	}
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+//	if (self->monsterinfo.aiflags & AI_DODGING)
+//		monster_done_dodge (self);
+
+	if (self->groundentity == NULL)
+		return;
+
+	// if we're reactivating or false dying, ignore the pain.
+	if (self->monsterinfo.currentmove == &stalker_move_false_death_end ||
+		self->monsterinfo.currentmove == &stalker_move_false_death_start )
+		return;
+
+	if (self->monsterinfo.currentmove == &stalker_move_false_death)
+	{
+		stalker_reactivate(self);
+		return;
+	}
+
+	if ((self->health > 0) && (self->health < (self->max_health / 4)))
+	{
+		if(qrandom() < (0.2 * skill->value))
+		{
+			if( !STALKER_ON_CEILING(self) || stalker_ok_to_transition(self) )
+			{
+//				gi.dprintf("starting false death sequence\n");
+				stalker_false_death_start(self);
+				return;
+			}
+		}	
+	}
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+//	gi.dprintf("stalker_pain\n");
+	if (damage > 10)		// don't react unless the damage was significant
+	{
+		// stalker should dodge jump periodically to help avoid damage.
+		if(self->groundentity && (qrandom() < 0.5))
+			stalker_dodge_jump(self);
+		else
+			self->monsterinfo.currentmove = &stalker_move_pain;
+
+		gi.sound (self, CHAN_WEAPON, sound_pain, 1, ATTN_NORM, 0);
+	}
+}
+
+
+// ******************
+// STALKER ATTACK
+// ******************
+
+//extern qboolean infront (edict_t *self, edict_t *other);
+
+void stalker_shoot_attack (edict_t *self)
+{
+	vec3_t	offset, start, f, r, dir;
+	vec3_t	end;
+	float	time, dist;
+	trace_t	trace;
+
+	if(!has_valid_enemy(self))
+		return;
+
+	if(self->groundentity && qrandom() < 0.33)
+	{
+		VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+		dist = VectorLength (dir);
+
+		if((dist > 256) || (qrandom() < 0.5))
+			stalker_do_pounce(self, self->enemy->s.origin);
+		else
+			stalker_jump_straightup (self);
+	}
+
+	// FIXME -- keep this but use a custom one
+//	if (!infront(self, self->enemy))
+//		return;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorSet (offset, 24, 0, 6);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	VectorSubtract(self->enemy->s.origin, start, dir);
+	if(qrandom() < (0.20 + 0.1 * skill->value))
+	{
+		dist = VectorLength(dir);
+		time = dist / 1000;
+		VectorMA(self->enemy->s.origin, time, self->enemy->velocity, end);
+		VectorSubtract(end, start, dir);
+	}
+	else
+		VectorCopy(self->enemy->s.origin, end);
+
+	trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+	if(trace.ent == self->enemy || trace.ent == WORLD)
+		monster_fire_blaster2(self, start, dir, 15, 800, MZ2_STALKER_BLASTER, EF_BLASTER);
+//	else
+//		gi.dprintf("blocked by entity %s\n", trace.ent->classname);
+}
+
+void stalker_shoot_attack2 (edict_t *self)
+{
+//	if (qrandom() < (0.4+(float)skill->value))
+//		stalker_shoot_attack (self);
+
+	if (qrandom() < (0.4 + (0.1 * (float)skill->value)))
+		stalker_shoot_attack (self);
+}
+
+mframe_t stalker_frames_shoot [] =
+{
+	ai_charge, 13, NULL,
+	ai_charge, 17, stalker_shoot_attack,
+	ai_charge, 21, NULL,
+	ai_charge, 18, stalker_shoot_attack2
+};
+mmove_t stalker_move_shoot = {FRAME_run01, FRAME_run04, stalker_frames_shoot, stalker_run};
+
+void stalker_attack_ranged (edict_t *self)
+{
+	if(!has_valid_enemy(self))
+		return;
+
+	// PMM - circle strafe stuff
+	if (qrandom() > (1.0 - (0.5/(float)(skill->value))))
+	{
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+	else
+	{
+		if (qrandom () <= 0.5) // switch directions
+			self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+		self->monsterinfo.attack_state = AS_SLIDING;
+	}
+	self->monsterinfo.currentmove = &stalker_move_shoot;
+}
+
+// ******************
+// close combat
+// ******************
+
+void stalker_swing_attack (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	if (fire_hit (self, aim, (5 + (rand() % 5)), 50))
+		if (self->s.frame < FRAME_attack08)
+			gi.sound (self, CHAN_WEAPON, sound_punch_hit2, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_WEAPON, sound_punch_hit1, 1, ATTN_NORM, 0);
+}
+
+mframe_t stalker_frames_swing_l [] =
+{
+	ai_charge, 2, NULL,
+	ai_charge, 4, NULL,
+	ai_charge, 6, NULL,
+	ai_charge, 10, NULL,
+
+	ai_charge, 5, stalker_swing_attack,
+	ai_charge, 5, NULL,
+	ai_charge, 5, NULL,
+	ai_charge, 5, NULL  // stalker_swing_check_l
+};
+mmove_t stalker_move_swing_l = {FRAME_attack01, FRAME_attack08, stalker_frames_swing_l, stalker_run};
+
+mframe_t stalker_frames_swing_r [] =
+{
+	ai_charge, 4, NULL,
+	ai_charge, 6, NULL,
+	ai_charge, 6, stalker_swing_attack,
+	ai_charge, 10, NULL,
+	ai_charge, 5, NULL	// stalker_swing_check_r
+};
+mmove_t stalker_move_swing_r = {FRAME_attack11, FRAME_attack15, stalker_frames_swing_r, stalker_run};
+
+void stalker_attack_melee (edict_t *self)
+{
+	if(!has_valid_enemy(self))
+		return;
+
+	if(qrandom() < 0.5)
+	{
+		self->monsterinfo.currentmove = &stalker_move_swing_l;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &stalker_move_swing_r;
+	}
+}
+
+
+// ******************
+// POUNCE
+// ******************
+
+//#define PI 3.14159
+#define RAD2DEG(x)	(x * (float)180.0 / (float)PI)
+#define DEG2RAD(x)	(x * (float)PI / (float)180.0)
+#define FAUX_GRAVITY	800.0
+
+// ====================
+// ====================
+void calcJumpAngle(vec3_t start, vec3_t end, float velocity, vec3_t angles)
+{
+	float	distV, distH;
+	float	one, cosU;
+	float	l, U;
+	vec3_t	dist;
+
+	VectorSubtract(end, start, dist);
+	distH = (float)sqrt(dist[0]*dist[0] + dist[1]*dist[1]);
+	distV = dist[2];
+	if(distV < 0)
+		distV = 0 - distV;
+
+	if(distV)
+	{
+		l = (float) sqrt(distH*distH + distV*distV);
+		U = (float) atan(distV / distH);
+		if(dist[2] > 0)
+			U = (float)0.0 - U;
+
+		angles[2] = 0.0;
+
+		cosU = (float)cos(U);
+		one = l * FAUX_GRAVITY * (cosU * cosU);
+		one = one / (velocity * velocity);
+		one = one - (float)sin(U);
+	//	one = ((l * FAUX_GRAVITY * (cosU * cosU)) / (velocity * velocity)) - (float)sin(U);
+		angles[0] = (float)asin(one);
+		if(isNaN(angles[0]))
+			angles[2] = 1.0;
+		angles[1] = (float)PI - angles[0];
+		if(isNaN(angles[1]))
+			angles[2] = 1.0;
+
+		angles[0] = RAD2DEG ( (angles[0] - U) / 2.0 );
+		angles[1] = RAD2DEG ( (angles[1] - U) / 2.0 );
+	}
+	else
+	{
+		l = (float) sqrt(distH*distH + distV*distV);
+
+		angles[2] = 0.0;
+
+		one = l * FAUX_GRAVITY;
+		one = one / (velocity * velocity);
+		angles[0] = (float)asin(one);
+		if(isNaN(angles[0]))
+			angles[2] = 1.0;
+		angles[1] = (float)PI - angles[0];
+		if(isNaN(angles[1]))
+			angles[2] = 1.0;
+
+		angles[0] = RAD2DEG ( (angles[0]) / 2.0 );
+		angles[1] = RAD2DEG ( (angles[1]) / 2.0 );
+	}
+}
+
+// ====================
+// ====================
+int stalker_check_lz (edict_t *self, edict_t *target, vec3_t dest)
+{
+	vec3_t	jumpLZ;
+
+	if( (gi.pointcontents (dest) & MASK_WATER) || (target->waterlevel))
+	{
+//		gi.dprintf ("you won't make me jump in water!\n");
+		return false;
+	}
+
+	if( !target->groundentity )
+	{
+//		gi.dprintf( "I'll wait until you land..\n");
+		return false;
+	}
+
+	// check under the player's four corners
+	// if they're not solid, bail.
+	jumpLZ[0] = self->enemy->mins[0];
+	jumpLZ[1] = self->enemy->mins[1];
+	jumpLZ[2] = self->enemy->mins[2] - 0.25;
+	if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) )
+		return false;
+
+	jumpLZ[0] = self->enemy->maxs[0];
+	jumpLZ[1] = self->enemy->mins[1];
+	if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) )
+		return false;
+
+	jumpLZ[0] = self->enemy->maxs[0];
+	jumpLZ[1] = self->enemy->maxs[1];
+	if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) )
+		return false;
+
+	jumpLZ[0] = self->enemy->mins[0];
+	jumpLZ[1] = self->enemy->maxs[1];
+	if( !(gi.pointcontents (jumpLZ) & MASK_SOLID) )
+		return false;
+
+	return true;
+}
+
+// ====================
+// ====================
+int stalker_do_pounce(edict_t *self, vec3_t dest)
+{
+	vec3_t	forward, right;
+	vec3_t	dist;
+	vec_t	length;
+	vec3_t	jumpAngles;
+	vec3_t	jumpLZ;
+	float	velocity = 400.1;
+	trace_t	trace;
+	int		preferHighJump;
+
+	// don't pounce when we're on the ceiling
+	if(STALKER_ON_CEILING(self))
+		return false;
+
+	if(!stalker_check_lz (self, self->enemy, dest))
+		return false;
+
+	VectorSubtract(dest, self->s.origin, dist);
+	
+	// make sure we're pointing in that direction 15deg margin of error.
+	vectoangles2 (dist, jumpAngles);
+	if(abs(jumpAngles[YAW] - self->s.angles[YAW]) > 45)
+		return false;			// not facing the player...
+
+	self->ideal_yaw = jumpAngles[YAW];
+	M_ChangeYaw(self);
+
+	length = VectorLength(dist);
+	if(length > 450)
+		return false;			// can't jump that far...
+
+	VectorCopy(dest, jumpLZ);
+
+	preferHighJump = 0;
+
+	// if we're having to jump up a distance, jump a little too high to compensate.
+	if(dist[2] >= 32.0)
+	{
+		preferHighJump = 1;
+		jumpLZ[2] += 32;
+	}
+
+	trace = gi.trace (self->s.origin, vec3_origin, vec3_origin, dest, self, MASK_MONSTERSOLID);
+	if((trace.fraction < 1) && (trace.ent != self->enemy))
+	{
+//		gi.dprintf("prefer high jump angle\n");
+		preferHighJump = 1; 
+	}
+
+	// find a valid angle/velocity combination
+	while(velocity <= 800)
+	{
+		calcJumpAngle(self->s.origin, jumpLZ, velocity, jumpAngles);
+		if((!isNaN(jumpAngles[0]))  || (!isNaN(jumpAngles[1])))
+			break;
+		
+		velocity+=200;
+	};
+
+	if(!preferHighJump && (!isNaN(jumpAngles[0])) )
+	{
+		AngleVectors (self->s.angles, forward, right, NULL);
+		VectorNormalize ( forward ) ;
+
+		VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[0])), self->velocity);
+		self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[0])) + (0.5 * sv_gravity->value * FRAMETIME);
+//		gi.dprintf("  pouncing! %0.1f,%0.1f (%0.1f)  --> %0.1f, %0.1f, %0.1f\n", 
+//				jumpAngles[0], jumpAngles[1], jumpAngles[0],
+//				self->velocity[0], self->velocity[1], self->velocity[2]);
+		return 1;
+	}
+
+	if(!isNaN(jumpAngles[1]))
+	{
+		AngleVectors (self->s.angles, forward, right, NULL);
+		VectorNormalize ( forward ) ;
+
+		VectorScale( forward, velocity * cos(DEG2RAD(jumpAngles[1])), self->velocity);
+		self->velocity[2] = velocity * sin(DEG2RAD(jumpAngles[1])) + (0.5 * sv_gravity->value * FRAMETIME);
+//		gi.dprintf("  pouncing! %0.1f,%0.1f (%0.1f)  --> %0.1f, %0.1f, %0.1f\n", 
+//				jumpAngles[0], jumpAngles[1], jumpAngles[1],
+//				self->velocity[0], self->velocity[1], self->velocity[2]);
+		return 1;
+	}
+
+//	gi.dprintf("  nan\n");
+	return 0;
+}
+
+// ******************
+// DODGE
+// ******************
+
+//===================
+// stalker_jump_straightup
+//===================
+void stalker_jump_straightup (edict_t *self)
+{
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	if(STALKER_ON_CEILING(self))
+	{
+		if(stalker_ok_to_transition(self))
+		{
+//			gi.dprintf("falling off ceiling %d\n", self->health);
+			self->gravityVector[2] = -1;
+			self->s.angles[2] += 180.0;
+			if(self->s.angles[2] > 360.0)
+				self->s.angles[2] -= 360.0;
+			self->groundentity = NULL;
+		}
+	}
+	else if(self->groundentity)	// make sure we're standing on SOMETHING...
+	{
+		self->velocity[0] += ((qrandom() * 10) - 5);
+		self->velocity[1] += ((qrandom() * 10) - 5);
+		self->velocity[2] += -400 * self->gravityVector[2];
+		if(stalker_ok_to_transition(self))
+		{
+//			gi.dprintf("falling TO ceiling %d\n", self->health);
+			self->gravityVector[2] = 1;
+			self->s.angles[2] = 180.0;
+			self->groundentity = NULL;
+		}
+	}
+}
+
+mframe_t stalker_frames_jump_straightup [] =
+{
+	ai_move, 1,  stalker_jump_straightup,
+	ai_move, 1,  stalker_jump_wait_land,
+	ai_move, -1, NULL,
+	ai_move, -1, NULL
+};
+
+mmove_t	stalker_move_jump_straightup = {FRAME_jump04, FRAME_jump07, stalker_frames_jump_straightup, stalker_run};
+
+//===================
+// stalker_dodge_jump - abstraction so pain function can trigger a dodge jump too without
+//		faking the inputs to stalker_dodge
+//===================
+void stalker_dodge_jump (edict_t *self)
+{
+	self->monsterinfo.currentmove = &stalker_move_jump_straightup;
+}
+
+mframe_t stalker_frames_dodge_run [] =
+{
+	ai_run, 13, NULL,
+	ai_run, 17, NULL,
+	ai_run, 21, NULL,
+	ai_run, 18, monster_done_dodge
+};
+mmove_t stalker_move_dodge_run = {FRAME_run01, FRAME_run04, stalker_frames_dodge_run, NULL};
+
+void stalker_dodge (edict_t *self, edict_t *attacker, float eta, trace_t *)
+{
+	if (!self->groundentity || self->health <= 0)
+		return;
+
+	if (!self->enemy)
+	{
+		self->enemy = attacker;
+		FoundTarget(self);
+		return;
+	}
+	
+	// PMM - don't bother if it's going to hit anyway; fix for weird in-your-face etas (I was
+	// seeing numbers like 13 and 14)
+	if ((eta < 0.1) || (eta > 5))
+		return;
+
+	// this will override the foundtarget call of stalker_run
+	stalker_dodge_jump(self);
+}
+
+
+// ******************
+// Jump onto / off of things
+// ******************
+
+//===================
+//===================
+void stalker_jump_down (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 100, forward, self->velocity);
+	VectorMA(self->velocity, 300, up, self->velocity);
+}
+
+//===================
+//===================
+void stalker_jump_up (edict_t *self)
+{
+	vec3_t	forward,up;
+
+	monster_jump_start (self);
+
+	AngleVectors (self->s.angles, forward, NULL, up);
+	VectorMA(self->velocity, 200, forward, self->velocity);
+	VectorMA(self->velocity, 450, up, self->velocity);
+}
+
+//===================
+//===================
+void stalker_jump_wait_land (edict_t *self)
+{
+	if ((qrandom() < (0.3 + (0.1*(float)(skill->value)))) && (level.time >= self->monsterinfo.attack_finished))
+	{
+		self->monsterinfo.attack_finished = level.time + 0.3;
+		stalker_shoot_attack(self);
+	}
+
+	if(self->groundentity == NULL)
+	{
+		self->gravity = 1.3;
+		self->monsterinfo.nextframe = self->s.frame;
+
+		if(monster_jump_finished (self))
+		{
+			self->gravity = 1;
+			self->monsterinfo.nextframe = self->s.frame + 1;
+		}
+	}
+	else 
+	{
+		self->gravity = 1;
+		self->monsterinfo.nextframe = self->s.frame + 1;
+	}
+}
+
+mframe_t stalker_frames_jump_up [] =
+{
+	ai_move, -8, NULL,
+	ai_move, -8, NULL,
+	ai_move, -8, NULL,
+	ai_move, -8, NULL,
+
+	ai_move, 0, stalker_jump_up,
+	ai_move, 0, stalker_jump_wait_land,
+	ai_move, 0, NULL
+};
+mmove_t stalker_move_jump_up = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_up, stalker_run };
+
+mframe_t stalker_frames_jump_down [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	
+	ai_move, 0, stalker_jump_down,
+	ai_move, 0, stalker_jump_wait_land,
+	ai_move, 0, NULL
+};
+mmove_t stalker_move_jump_down = { FRAME_jump01, FRAME_jump07, stalker_frames_jump_down, stalker_run };
+
+//============
+// stalker_jump - this is only used for jumping onto or off of things. for dodge jumping,
+//		use stalker_dodge_jump
+//============
+void stalker_jump (edict_t *self)
+{
+	if(!self->enemy)
+		return;
+
+	if(self->enemy->s.origin[2] >= self->s.origin[2])
+	{
+//		gi.dprintf("stalker_jump_up\n");
+		self->monsterinfo.currentmove = &stalker_move_jump_up;
+	}
+	else
+	{
+//		gi.dprintf("stalker_jump_down\n");
+		self->monsterinfo.currentmove = &stalker_move_jump_down;
+	}
+}
+
+
+// ******************
+// Blocked
+// ******************
+
+qboolean stalker_blocked (edict_t *self, float dist)
+{
+	qboolean	onCeiling;
+
+//	gi.dprintf("stalker_blocked\n");
+	if(!has_valid_enemy(self))
+		return false;
+
+	onCeiling = false;
+	if(self->gravityVector[2] > 0)
+		onCeiling = true;
+
+	if(!onCeiling)
+	{
+		if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) ))
+		{
+//			gi.dprintf("blocked: shooting\n");
+			return true;
+		}
+
+		if(visible (self, self->enemy))
+		{
+//			gi.dprintf("blocked: jumping at player!\n");
+			stalker_do_pounce(self, self->enemy->s.origin);
+			return true;
+		}
+
+		if(blocked_checkjump (self, dist, 256, 68))
+		{
+//			gi.dprintf("blocked: jumping up/down\n");
+			stalker_jump (self);
+			return true;
+		}
+
+		if(blocked_checkplat (self, dist))
+			return true;
+	}
+	else
+	{
+		if(blocked_checkshot(self, 0.25 + (0.05 * skill->value) ))
+		{
+//			gi.dprintf("blocked: shooting\n");
+			return true;
+		}	
+		else if(stalker_ok_to_transition(self))
+		{
+			self->gravityVector[2] = -1;
+			self->s.angles[2] += 180.0;
+			if(self->s.angles[2] > 360.0)
+				self->s.angles[2] -= 360.0;
+			self->groundentity = NULL;
+			
+//			gi.dprintf("falling off ceiling\n");
+			return true;
+		}
+//		else
+//			gi.dprintf("Not OK to fall!\n");
+	}
+
+	return false;
+}
+
+// ******************
+// Death
+// ******************
+
+void stalker_dead (edict_t *self)
+{
+	VectorSet (self->mins, -28, -28, -18);
+	VectorSet (self->maxs, 28, 28, -4);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+//	drawbbox(self);
+}
+
+mframe_t stalker_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, -5,	 NULL,
+	ai_move, -10,	 NULL,
+	ai_move, -20,	 NULL,
+	
+	ai_move, -10,	 NULL,
+	ai_move, -10,	 NULL,
+	ai_move, -5,	 NULL,
+	ai_move, -5,	 NULL,
+
+	ai_move, 0,	 NULL
+};
+mmove_t stalker_move_death = {FRAME_death01, FRAME_death09, stalker_frames_death, stalker_dead};
+
+void stalker_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+//	gi.dprintf("stalker_die: %d\n", self->health);
+
+// dude bit it, make him fall!
+	self->movetype = MOVETYPE_TOSS;
+	self->s.angles[2] = 0;
+	VectorSet(self->gravityVector, 0, 0, -1);
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &stalker_move_death;
+}
+
+
+// ******************
+// SPAWN
+// ******************
+
+/*QUAKED monster_stalker (1 .5 0) (-28 -28 -18) (28 28 18) Ambush Trigger_Spawn Sight OnRoof
+Spider Monster
+
+  ONROOF - Monster starts sticking to the roof.
+*/
+void SP_monster_stalker (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain = gi.soundindex ("stalker/pain.wav");	
+	sound_die = gi.soundindex ("stalker/death.wav");	
+	sound_sight = gi.soundindex("stalker/sight.wav");
+	sound_punch_hit1 = gi.soundindex ("stalker/melee1.wav");
+	sound_punch_hit2 = gi.soundindex ("stalker/melee2.wav");
+	sound_idle = gi.soundindex ("stalker/idle.wav");
+
+	// PMM - precache bolt2
+	gi.modelindex ("models/proj/laser2/tris.md2");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/stalker/tris.md2");
+	VectorSet (self->mins, -28, -28, -18);
+	VectorSet (self->maxs, 28, 28, 18);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 250;
+	self->gib_health = -50;		// FIXME 
+	self->mass = 250;
+
+	self->pain = stalker_pain;
+	self->die = stalker_die;
+
+	self->monsterinfo.stand = stalker_stand;
+	self->monsterinfo.walk = stalker_walk;
+	self->monsterinfo.run = stalker_run;
+	self->monsterinfo.attack = stalker_attack_ranged;
+	self->monsterinfo.sight = stalker_sight;
+	self->monsterinfo.idle = stalker_idle;
+	self->monsterinfo.dodge = stalker_dodge;
+	self->monsterinfo.blocked = stalker_blocked;
+	self->monsterinfo.melee = stalker_attack_melee;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &stalker_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	self->monsterinfo.aiflags |= AI_WALK_WALLS;
+
+	if(self->spawnflags & 8)
+	{
+		self->s.angles[2] = 180;
+		self->gravityVector[2] = 1;
+	}
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_stalker.h
@@ -1,0 +1,99 @@
+// /expanse/quake2/xpack/models/monsters/stalker
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_idle01          	0
+#define FRAME_idle02          	1
+#define FRAME_idle03          	2
+#define FRAME_idle04          	3
+#define FRAME_idle05          	4
+#define FRAME_idle06          	5
+#define FRAME_idle07          	6
+#define FRAME_idle08          	7
+#define FRAME_idle09          	8
+#define FRAME_idle10          	9
+#define FRAME_idle11          	10
+#define FRAME_idle12          	11
+#define FRAME_idle13          	12
+#define FRAME_idle14          	13
+#define FRAME_idle15          	14
+#define FRAME_idle16          	15
+#define FRAME_idle17          	16
+#define FRAME_idle18          	17
+#define FRAME_idle19          	18
+#define FRAME_idle20          	19
+#define FRAME_idle21          	20
+#define FRAME_idle201         	21
+#define FRAME_idle202         	22
+#define FRAME_idle203         	23
+#define FRAME_idle204         	24
+#define FRAME_idle205         	25
+#define FRAME_idle206         	26
+#define FRAME_idle207         	27
+#define FRAME_idle208         	28
+#define FRAME_idle209         	29
+#define FRAME_idle210         	30
+#define FRAME_idle211         	31
+#define FRAME_idle212         	32
+#define FRAME_idle213         	33
+#define FRAME_walk01          	34
+#define FRAME_walk02          	35
+#define FRAME_walk03          	36
+#define FRAME_walk04          	37
+#define FRAME_walk05          	38
+#define FRAME_walk06          	39
+#define FRAME_walk07          	40
+#define FRAME_walk08          	41
+#define FRAME_jump01          	42
+#define FRAME_jump02          	43
+#define FRAME_jump03          	44
+#define FRAME_jump04          	45
+#define FRAME_jump05          	46
+#define FRAME_jump06          	47
+#define FRAME_jump07          	48
+#define FRAME_run01           	49
+#define FRAME_run02           	50
+#define FRAME_run03           	51
+#define FRAME_run04           	52
+#define FRAME_attack01        	53
+#define FRAME_attack02        	54
+#define FRAME_attack03        	55
+#define FRAME_attack04        	56
+#define FRAME_attack05        	57
+#define FRAME_attack06        	58
+#define FRAME_attack07        	59
+#define FRAME_attack08        	60
+#define FRAME_attack11        	61
+#define FRAME_attack12        	62
+#define FRAME_attack13        	63
+#define FRAME_attack14        	64
+#define FRAME_attack15        	65
+#define FRAME_pain01          	66
+#define FRAME_pain02          	67
+#define FRAME_pain03          	68
+#define FRAME_pain04          	69
+#define FRAME_death01         	70
+#define FRAME_death02         	71
+#define FRAME_death03         	72
+#define FRAME_death04         	73
+#define FRAME_death05         	74
+#define FRAME_death06         	75
+#define FRAME_death07         	76
+#define FRAME_death08         	77
+#define FRAME_death09         	78
+#define FRAME_twitch01        	79
+#define FRAME_twitch02        	80
+#define FRAME_twitch03        	81
+#define FRAME_twitch04        	82
+#define FRAME_twitch05        	83
+#define FRAME_twitch06        	84
+#define FRAME_twitch07        	85
+#define FRAME_twitch08        	86
+#define FRAME_twitch09        	87
+#define FRAME_twitch10        	88
+#define FRAME_reactive01      	89
+#define FRAME_reactive02      	90
+#define FRAME_reactive03      	91
+#define FRAME_reactive04      	92
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_supertank.c
@@ -1,0 +1,721 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_supertank.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_search2;
+
+static	int	tread_sound;
+
+void BossExplode (edict_t *self);
+
+void TreadSound (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void supertank_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+}
+
+
+void supertank_dead (edict_t *self);
+void supertankRocket (edict_t *self);
+void supertankMachineGun (edict_t *self);
+void supertank_reattack1(edict_t *self);
+
+
+//
+// stand
+//
+
+mframe_t supertank_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	supertank_move_stand = {FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, NULL};
+	
+void supertank_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &supertank_move_stand;
+}
+
+
+mframe_t supertank_frames_run [] =
+{
+	ai_run, 12,	TreadSound,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL
+};
+mmove_t	supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL};
+
+//
+// walk
+//
+
+
+mframe_t supertank_frames_forward [] =
+{
+	ai_walk, 4,	TreadSound,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL
+};
+mmove_t	supertank_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, NULL};
+
+void supertank_forward (edict_t *self)
+{
+		self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void supertank_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void supertank_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &supertank_move_stand;
+	else
+		self->monsterinfo.currentmove = &supertank_move_run;
+}
+
+mframe_t supertank_frames_turn_right [] =
+{
+	ai_move,	0,	TreadSound,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_turn_right = {FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run};
+
+mframe_t supertank_frames_turn_left [] =
+{
+	ai_move,	0,	TreadSound,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_turn_left = {FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run};
+
+
+mframe_t supertank_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run};
+
+mframe_t supertank_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run};
+
+mframe_t supertank_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run};
+
+mframe_t supertank_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode
+};
+mmove_t supertank_move_death = {FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead};
+
+mframe_t supertank_frames_backward[] =
+{
+	ai_walk, 0,	TreadSound,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL
+};
+mmove_t	supertank_move_backward = {FRAME_backwd_1, FRAME_backwd_18, supertank_frames_backward, NULL};
+
+mframe_t supertank_frames_attack4[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run};
+
+mframe_t supertank_frames_attack3[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, supertank_frames_attack3, supertank_run};
+
+mframe_t supertank_frames_attack2[]=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run};
+
+mframe_t supertank_frames_attack1[]=
+{
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+
+};
+mmove_t supertank_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1};
+
+mframe_t supertank_frames_end_attack1[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run};
+
+
+void supertank_reattack1(edict_t *self)
+{
+	if (visible(self, self->enemy))
+		if (qrandom() < 0.9)
+			self->monsterinfo.currentmove = &supertank_move_attack1;
+		else
+			self->monsterinfo.currentmove = &supertank_move_end_attack1;	
+	else
+		self->monsterinfo.currentmove = &supertank_move_end_attack1;
+}
+
+void supertank_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames
+	if (damage <=25)
+		if (qrandom()<0.2)
+			return;
+
+	// Don't go into pain if he's firing his rockets
+	if (skill->value >= 2)
+		if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) )
+			return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain1;
+	}
+	else if (damage <= 25)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain3;
+	}
+};
+
+
+void supertankRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	if (self->s.frame == FRAME_attak2_8)
+		flash_number = MZ2_SUPERTANK_ROCKET_1;
+	else if (self->s.frame == FRAME_attak2_11)
+		flash_number = MZ2_SUPERTANK_ROCKET_2;
+	else // (self->s.frame == FRAME_attak2_14)
+		flash_number = MZ2_SUPERTANK_ROCKET_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_rocket (self, start, dir, 50, 500, flash_number);
+}	
+
+void supertankMachineGun (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1);
+
+	//FIXME!!!
+	dir[0] = 0;
+	dir[1] = self->s.angles[1];
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		VectorMA (vec, 0, self->enemy->velocity, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, forward);
+		VectorNormalize (forward);
+  }
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}	
+
+
+void supertank_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+	//float	r;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+
+	//r = qrandom();
+
+	// Attack 1 == Chaingun
+	// Attack 2 == Rocket Launcher
+
+	if (range <= 160)
+	{
+		self->monsterinfo.currentmove = &supertank_move_attack1;
+	}
+	else
+	{	// fire rockets more often at distance
+		if (qrandom() < 0.3)
+			self->monsterinfo.currentmove = &supertank_move_attack1;
+		else
+			self->monsterinfo.currentmove = &supertank_move_attack2;
+	}
+}
+
+
+//
+// death
+//
+
+void supertank_dead (edict_t *self)
+{
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void BossExplode (edict_t *self)
+{
+	vec3_t	org;
+	int		n;
+
+	self->think = BossExplode;
+	VectorCopy (self->s.origin, org);
+	org[2] += 24 + (rand()&15);
+	switch (self->count++)
+	{
+	case 0:
+		org[0] -= 24;
+		org[1] -= 24;
+		break;
+	case 1:
+		org[0] += 24;
+		org[1] += 24;
+		break;
+	case 2:
+		org[0] += 24;
+		org[1] -= 24;
+		break;
+	case 3:
+		org[0] -= 24;
+		org[1] += 24;
+		break;
+	case 4:
+		org[0] -= 48;
+		org[1] -= 48;
+		break;
+	case 5:
+		org[0] += 48;
+		org[1] += 48;
+		break;
+	case 6:
+		org[0] -= 48;
+		org[1] += 48;
+		break;
+	case 7:
+		org[0] += 48;
+		org[1] -= 48;
+		break;
+	case 8:
+		self->s.sound = 0;
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC);
+		for (n= 0; n < 8; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (org);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	self->nextthink = level.time + 0.1;
+}
+
+
+void supertank_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &supertank_move_death;
+}
+
+
+//===========
+//PGM
+qboolean supertank_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+//
+// monster_supertank
+//
+
+/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_supertank (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav");
+	sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav");
+	sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav");
+	sound_death = gi.soundindex ("bosstank/btkdeth1.wav");
+	sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav");
+	sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav");
+
+//	self->s.sound = gi.soundindex ("bosstank/btkengn1.wav");
+	tread_sound = gi.soundindex ("bosstank/btkengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2");
+	VectorSet (self->mins, -64, -64, 0);
+	VectorSet (self->maxs, 64, 64, 112);
+
+	self->health = 1500;
+	self->gib_health = -500;
+	self->mass = 800;
+
+	self->pain = supertank_pain;
+	self->die = supertank_die;
+	self->monsterinfo.stand = supertank_stand;
+	self->monsterinfo.walk = supertank_walk;
+	self->monsterinfo.run = supertank_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = supertank_attack;
+	self->monsterinfo.search = supertank_search;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+	self->monsterinfo.blocked = supertank_blocked;		//PGM
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &supertank_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+
+	//PMM
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+	//pmm
+}
--- /dev/null
+++ b/rogue/m_supertank.h
@@ -1,0 +1,260 @@
+// G:\quake2\baseq2\models/monsters/boss1/backup
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak1_1        	0
+#define FRAME_attak1_2        	1
+#define FRAME_attak1_3        	2
+#define FRAME_attak1_4        	3
+#define FRAME_attak1_5        	4
+#define FRAME_attak1_6        	5
+#define FRAME_attak1_7        	6
+#define FRAME_attak1_8        	7
+#define FRAME_attak1_9        	8
+#define FRAME_attak1_10       	9
+#define FRAME_attak1_11       	10
+#define FRAME_attak1_12       	11
+#define FRAME_attak1_13       	12
+#define FRAME_attak1_14       	13
+#define FRAME_attak1_15       	14
+#define FRAME_attak1_16       	15
+#define FRAME_attak1_17       	16
+#define FRAME_attak1_18       	17
+#define FRAME_attak1_19       	18
+#define FRAME_attak1_20       	19
+#define FRAME_attak2_1        	20
+#define FRAME_attak2_2        	21
+#define FRAME_attak2_3        	22
+#define FRAME_attak2_4        	23
+#define FRAME_attak2_5        	24
+#define FRAME_attak2_6        	25
+#define FRAME_attak2_7        	26
+#define FRAME_attak2_8        	27
+#define FRAME_attak2_9        	28
+#define FRAME_attak2_10       	29
+#define FRAME_attak2_11       	30
+#define FRAME_attak2_12       	31
+#define FRAME_attak2_13       	32
+#define FRAME_attak2_14       	33
+#define FRAME_attak2_15       	34
+#define FRAME_attak2_16       	35
+#define FRAME_attak2_17       	36
+#define FRAME_attak2_18       	37
+#define FRAME_attak2_19       	38
+#define FRAME_attak2_20       	39
+#define FRAME_attak2_21       	40
+#define FRAME_attak2_22       	41
+#define FRAME_attak2_23       	42
+#define FRAME_attak2_24       	43
+#define FRAME_attak2_25       	44
+#define FRAME_attak2_26       	45
+#define FRAME_attak2_27       	46
+#define FRAME_attak3_1        	47
+#define FRAME_attak3_2        	48
+#define FRAME_attak3_3        	49
+#define FRAME_attak3_4        	50
+#define FRAME_attak3_5        	51
+#define FRAME_attak3_6        	52
+#define FRAME_attak3_7        	53
+#define FRAME_attak3_8        	54
+#define FRAME_attak3_9        	55
+#define FRAME_attak3_10       	56
+#define FRAME_attak3_11       	57
+#define FRAME_attak3_12       	58
+#define FRAME_attak3_13       	59
+#define FRAME_attak3_14       	60
+#define FRAME_attak3_15       	61
+#define FRAME_attak3_16       	62
+#define FRAME_attak3_17       	63
+#define FRAME_attak3_18       	64
+#define FRAME_attak3_19       	65
+#define FRAME_attak3_20       	66
+#define FRAME_attak3_21       	67
+#define FRAME_attak3_22       	68
+#define FRAME_attak3_23       	69
+#define FRAME_attak3_24       	70
+#define FRAME_attak3_25       	71
+#define FRAME_attak3_26       	72
+#define FRAME_attak3_27       	73
+#define FRAME_attak4_1        	74
+#define FRAME_attak4_2        	75
+#define FRAME_attak4_3        	76
+#define FRAME_attak4_4        	77
+#define FRAME_attak4_5        	78
+#define FRAME_attak4_6        	79
+#define FRAME_backwd_1        	80
+#define FRAME_backwd_2        	81
+#define FRAME_backwd_3        	82
+#define FRAME_backwd_4        	83
+#define FRAME_backwd_5        	84
+#define FRAME_backwd_6        	85
+#define FRAME_backwd_7        	86
+#define FRAME_backwd_8        	87
+#define FRAME_backwd_9        	88
+#define FRAME_backwd_10       	89
+#define FRAME_backwd_11       	90
+#define FRAME_backwd_12       	91
+#define FRAME_backwd_13       	92
+#define FRAME_backwd_14       	93
+#define FRAME_backwd_15       	94
+#define FRAME_backwd_16       	95
+#define FRAME_backwd_17       	96
+#define FRAME_backwd_18       	97
+#define FRAME_death_1         	98
+#define FRAME_death_2         	99
+#define FRAME_death_3         	100
+#define FRAME_death_4         	101
+#define FRAME_death_5         	102
+#define FRAME_death_6         	103
+#define FRAME_death_7         	104
+#define FRAME_death_8         	105
+#define FRAME_death_9         	106
+#define FRAME_death_10        	107
+#define FRAME_death_11        	108
+#define FRAME_death_12        	109
+#define FRAME_death_13        	110
+#define FRAME_death_14        	111
+#define FRAME_death_15        	112
+#define FRAME_death_16        	113
+#define FRAME_death_17        	114
+#define FRAME_death_18        	115
+#define FRAME_death_19        	116
+#define FRAME_death_20        	117
+#define FRAME_death_21        	118
+#define FRAME_death_22        	119
+#define FRAME_death_23        	120
+#define FRAME_death_24        	121
+#define FRAME_death_31        	122
+#define FRAME_death_32        	123
+#define FRAME_death_33        	124
+#define FRAME_death_45        	125
+#define FRAME_death_46        	126
+#define FRAME_death_47        	127
+#define FRAME_forwrd_1        	128
+#define FRAME_forwrd_2        	129
+#define FRAME_forwrd_3        	130
+#define FRAME_forwrd_4        	131
+#define FRAME_forwrd_5        	132
+#define FRAME_forwrd_6        	133
+#define FRAME_forwrd_7        	134
+#define FRAME_forwrd_8        	135
+#define FRAME_forwrd_9        	136
+#define FRAME_forwrd_10       	137
+#define FRAME_forwrd_11       	138
+#define FRAME_forwrd_12       	139
+#define FRAME_forwrd_13       	140
+#define FRAME_forwrd_14       	141
+#define FRAME_forwrd_15       	142
+#define FRAME_forwrd_16       	143
+#define FRAME_forwrd_17       	144
+#define FRAME_forwrd_18       	145
+#define FRAME_left_1          	146
+#define FRAME_left_2          	147
+#define FRAME_left_3          	148
+#define FRAME_left_4          	149
+#define FRAME_left_5          	150
+#define FRAME_left_6          	151
+#define FRAME_left_7          	152
+#define FRAME_left_8          	153
+#define FRAME_left_9          	154
+#define FRAME_left_10         	155
+#define FRAME_left_11         	156
+#define FRAME_left_12         	157
+#define FRAME_left_13         	158
+#define FRAME_left_14         	159
+#define FRAME_left_15         	160
+#define FRAME_left_16         	161
+#define FRAME_left_17         	162
+#define FRAME_left_18         	163
+#define FRAME_pain1_1         	164
+#define FRAME_pain1_2         	165
+#define FRAME_pain1_3         	166
+#define FRAME_pain1_4         	167
+#define FRAME_pain2_5         	168
+#define FRAME_pain2_6         	169
+#define FRAME_pain2_7         	170
+#define FRAME_pain2_8         	171
+#define FRAME_pain3_9         	172
+#define FRAME_pain3_10        	173
+#define FRAME_pain3_11        	174
+#define FRAME_pain3_12        	175
+#define FRAME_right_1         	176
+#define FRAME_right_2         	177
+#define FRAME_right_3         	178
+#define FRAME_right_4         	179
+#define FRAME_right_5         	180
+#define FRAME_right_6         	181
+#define FRAME_right_7         	182
+#define FRAME_right_8         	183
+#define FRAME_right_9         	184
+#define FRAME_right_10        	185
+#define FRAME_right_11        	186
+#define FRAME_right_12        	187
+#define FRAME_right_13        	188
+#define FRAME_right_14        	189
+#define FRAME_right_15        	190
+#define FRAME_right_16        	191
+#define FRAME_right_17        	192
+#define FRAME_right_18        	193
+#define FRAME_stand_1         	194
+#define FRAME_stand_2         	195
+#define FRAME_stand_3         	196
+#define FRAME_stand_4         	197
+#define FRAME_stand_5         	198
+#define FRAME_stand_6         	199
+#define FRAME_stand_7         	200
+#define FRAME_stand_8         	201
+#define FRAME_stand_9         	202
+#define FRAME_stand_10        	203
+#define FRAME_stand_11        	204
+#define FRAME_stand_12        	205
+#define FRAME_stand_13        	206
+#define FRAME_stand_14        	207
+#define FRAME_stand_15        	208
+#define FRAME_stand_16        	209
+#define FRAME_stand_17        	210
+#define FRAME_stand_18        	211
+#define FRAME_stand_19        	212
+#define FRAME_stand_20        	213
+#define FRAME_stand_21        	214
+#define FRAME_stand_22        	215
+#define FRAME_stand_23        	216
+#define FRAME_stand_24        	217
+#define FRAME_stand_25        	218
+#define FRAME_stand_26        	219
+#define FRAME_stand_27        	220
+#define FRAME_stand_28        	221
+#define FRAME_stand_29        	222
+#define FRAME_stand_30        	223
+#define FRAME_stand_31        	224
+#define FRAME_stand_32        	225
+#define FRAME_stand_33        	226
+#define FRAME_stand_34        	227
+#define FRAME_stand_35        	228
+#define FRAME_stand_36        	229
+#define FRAME_stand_37        	230
+#define FRAME_stand_38        	231
+#define FRAME_stand_39        	232
+#define FRAME_stand_40        	233
+#define FRAME_stand_41        	234
+#define FRAME_stand_42        	235
+#define FRAME_stand_43        	236
+#define FRAME_stand_44        	237
+#define FRAME_stand_45        	238
+#define FRAME_stand_46        	239
+#define FRAME_stand_47        	240
+#define FRAME_stand_48        	241
+#define FRAME_stand_49        	242
+#define FRAME_stand_50        	243
+#define FRAME_stand_51        	244
+#define FRAME_stand_52        	245
+#define FRAME_stand_53        	246
+#define FRAME_stand_54        	247
+#define FRAME_stand_55        	248
+#define FRAME_stand_56        	249
+#define FRAME_stand_57        	250
+#define FRAME_stand_58        	251
+#define FRAME_stand_59        	252
+#define FRAME_stand_60        	253
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_tank.c
@@ -1,0 +1,1029 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_tank.h"
+
+
+void tank_refire_rocket (edict_t *self);
+void tank_doattack_rocket (edict_t *self);
+void tank_reattack_blaster (edict_t *self);
+
+static int	sound_thud;
+static int	sound_pain;
+static int	sound_idle;
+static int	sound_die;
+static int	sound_step;
+static int	sound_sight;
+static int	sound_windup;
+static int	sound_strike;
+
+//
+// misc
+//
+
+void tank_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+
+void tank_footstep (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0);
+}
+
+void tank_thud (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0);
+}
+
+void tank_windup (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
+}
+
+void tank_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//
+// stand
+//
+
+mframe_t tank_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	tank_move_stand = {FRAME_stand01, FRAME_stand30, tank_frames_stand, NULL};
+	
+void tank_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &tank_move_stand;
+}
+
+
+//
+// walk
+//
+
+void tank_walk (edict_t *self);
+
+mframe_t tank_frames_start_walk [] =
+{
+	ai_walk,  0, NULL,
+	ai_walk,  6, NULL,
+	ai_walk,  6, NULL,
+	ai_walk, 11, tank_footstep
+};
+mmove_t	tank_move_start_walk = {FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk};
+
+mframe_t tank_frames_walk [] =
+{
+	ai_walk, 4,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 3,	NULL,
+	ai_walk, 2,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	tank_footstep,
+	ai_walk, 3,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 7,	NULL,
+	ai_walk, 7,	NULL,
+	ai_walk, 6,	NULL,
+	ai_walk, 6,	tank_footstep
+};
+mmove_t	tank_move_walk = {FRAME_walk05, FRAME_walk20, tank_frames_walk, NULL};
+
+mframe_t tank_frames_stop_walk [] =
+{
+	ai_walk,  3, NULL,
+	ai_walk,  3, NULL,
+	ai_walk,  2, NULL,
+	ai_walk,  2, NULL,
+	ai_walk,  4, tank_footstep
+};
+mmove_t	tank_move_stop_walk = {FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand};
+
+void tank_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &tank_move_walk;
+}
+
+
+//
+// run
+//
+
+void tank_run (edict_t *self);
+
+mframe_t tank_frames_start_run [] =
+{
+	ai_run,  0, NULL,
+	ai_run,  6, NULL,
+	ai_run,  6, NULL,
+	ai_run, 11, tank_footstep
+};
+mmove_t	tank_move_start_run = {FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run};
+
+mframe_t tank_frames_run [] =
+{
+	ai_run, 4,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 3,	NULL,
+	ai_run, 2,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 4,	NULL,
+	ai_run, 4,	tank_footstep,
+	ai_run, 3,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 4,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 7,	NULL,
+	ai_run, 7,	NULL,
+	ai_run, 6,	NULL,
+	ai_run, 6,	tank_footstep
+};
+mmove_t	tank_move_run = {FRAME_walk05, FRAME_walk20, tank_frames_run, NULL};
+
+mframe_t tank_frames_stop_run [] =
+{
+	ai_run,  3, NULL,
+	ai_run,  3, NULL,
+	ai_run,  2, NULL,
+	ai_run,  2, NULL,
+	ai_run,  4, tank_footstep
+};
+mmove_t	tank_move_stop_run = {FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk};
+
+void tank_run (edict_t *self)
+{
+	if (self->enemy && self->enemy->client)
+		self->monsterinfo.aiflags |= AI_BRUTAL;
+	else
+		self->monsterinfo.aiflags &= ~AI_BRUTAL;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &tank_move_stand;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &tank_move_walk ||
+		self->monsterinfo.currentmove == &tank_move_start_run)
+	{
+		self->monsterinfo.currentmove = &tank_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &tank_move_start_run;
+	}
+}
+
+//
+// pain
+//
+
+mframe_t tank_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t tank_move_pain1 = {FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run};
+
+mframe_t tank_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t tank_move_pain2 = {FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run};
+
+mframe_t tank_frames_pain3 [] =
+{
+	ai_move, -7, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  tank_footstep
+};
+mmove_t	tank_move_pain3 = {FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run};
+
+
+void tank_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum |= 1;
+
+	if (damage <= 10)
+		return;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	if (damage <= 30)
+		if (qrandom() > 0.2)
+			return;
+	
+	// If hard or nightmare, don't go into pain while attacking
+	if ( skill->value >= 2)
+	{
+		if ( (self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330) )
+			return;
+		if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116) )
+			return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+	gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	// PMM - blindfire cleanup
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+	// pmm
+
+	if (damage <= 30)
+		self->monsterinfo.currentmove = &tank_move_pain1;
+	else if (damage <= 60)
+		self->monsterinfo.currentmove = &tank_move_pain2;
+	else
+		self->monsterinfo.currentmove = &tank_move_pain3;
+};
+
+
+//
+// attacks
+//
+
+void TankBlaster (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	end;
+	vec3_t	dir;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	if (self->s.frame == FRAME_attak110)
+		flash_number = MZ2_TANK_BLASTER_1;
+	else if (self->s.frame == FRAME_attak113)
+		flash_number = MZ2_TANK_BLASTER_2;
+	else // (self->s.frame == FRAME_attak116)
+		flash_number = MZ2_TANK_BLASTER_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 30, 800, flash_number, EF_BLASTER);
+}	
+
+void TankStrike (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0);
+}	
+
+void TankRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+	trace_t	trace;				// PGM
+	int		rocketSpeed;		// PGM
+	// pmm - blindfire support
+	vec3_t	target;
+	qboolean blindfire;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	// pmm - blindfire check
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+		blindfire = true;
+	else
+		blindfire = false;
+
+	if (self->s.frame == FRAME_attak324)
+		flash_number = MZ2_TANK_ROCKET_1;
+	else if (self->s.frame == FRAME_attak327)
+		flash_number = MZ2_TANK_ROCKET_2;
+	else // (self->s.frame == FRAME_attak330)
+		flash_number = MZ2_TANK_ROCKET_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	rocketSpeed = 500 + (100 * skill->value);	// PGM rock & roll.... :)
+
+		// PMM
+	if (blindfire)
+		VectorCopy (self->monsterinfo.blind_fire_target, target);
+	else
+		VectorCopy (self->enemy->s.origin, target);
+	// pmm
+
+//	VectorCopy (self->enemy->s.origin, vec);
+//	vec[2] += self->enemy->viewheight;
+//	VectorSubtract (vec, start, dir);
+
+//PGM
+	// PMM - blindfire shooting
+	if (blindfire)
+	{
+		VectorCopy (target, vec);
+		VectorSubtract (vec, start, dir);
+	}
+	// pmm
+	// don't shoot at feet if they're above me.
+	else if(qrandom() < 0.66 || (start[2] < self->enemy->absmin[2]))
+	{
+//		gi.dprintf("normal shot\n");
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, dir);
+	}
+	else
+	{
+//		gi.dprintf("shooting at feet!\n");
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] = self->enemy->absmin[2];
+		VectorSubtract (vec, start, dir);
+	}
+//PGM
+	
+//======
+//PMM - lead target  (not when blindfiring)
+	// 20, 35, 50, 65 chance of leading
+	if((!blindfire) && ((qrandom() < (0.2 + ((3 - skill->value) * 0.15)))))
+	{
+		float	dist;
+		float	time;
+
+//		gi.dprintf ("leading target\n");
+		dist = VectorLength (dir);
+		time = dist/rocketSpeed;
+		VectorMA(vec, time, self->enemy->velocity, vec);
+		VectorSubtract(vec, start, dir);
+	}
+//PMM - lead target
+//======
+
+	VectorNormalize (dir);
+
+//			gi.WriteByte (svc_temp_entity);
+//			gi.WriteByte (TE_DEBUGTRAIL);
+//			gi.WritePosition (start);
+//			gi.WritePosition (vec);
+//			gi.multicast (start, MULTICAST_ALL);
+
+	// pmm blindfire doesn't check target (done in checkattack)
+	// paranoia, make sure we're not shooting a target right next to us
+	trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+	if (blindfire)
+	{
+		// blindfire has different fail criteria for the trace
+		if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+			monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number);
+		else 
+		{
+			// try shifting the target to the left a little (to help counter large offset)
+			VectorCopy (target, vec);
+			VectorMA (vec, -20, right, vec);
+			VectorSubtract(vec, start, dir);
+			VectorNormalize (dir);
+			trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+			if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+				monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number);
+			else 
+			{
+				// ok, that failed.  try to the right
+				VectorCopy (target, vec);
+				VectorMA (vec, 20, right, vec);
+				VectorSubtract(vec, start, dir);
+				VectorNormalize (dir);
+				trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+				if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5)))
+					monster_fire_rocket (self, start, dir, 50, rocketSpeed, flash_number);
+				else if ((g_showlogic) && (g_showlogic->value))
+					// ok, I give up
+					gi.dprintf ("tank avoiding blindfire shot\n");
+			}
+		}
+	}
+	else
+	{
+		trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT);
+		if(trace.ent == self->enemy || trace.ent == WORLD)
+		{
+			if(trace.fraction > 0.5 || (trace.ent && trace.ent->client))
+				monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
+	//		else
+	//			gi.dprintf("didn't make it halfway to target...aborting\n");
+		}
+	}
+}	
+
+void TankMachineGun (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	if(!self->enemy || !self->enemy->inuse)		//PGM
+		return;									//PGM
+
+	flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, vec);
+		vectoangles (vec, vec);
+		dir[0] = vec[0];
+	}
+	else
+	{
+		dir[0] = 0;
+	}
+	if (self->s.frame <= FRAME_attak415)
+		dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411);
+	else
+		dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419);
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, NULL, NULL);
+
+	monster_fire_bullet (self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}	
+
+
+mframe_t tank_frames_attack_blast [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,		// 10
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster			// 16
+};
+mmove_t tank_move_attack_blast = {FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster};
+
+mframe_t tank_frames_reattack_blast [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster			// 16
+};
+mmove_t tank_move_reattack_blast = {FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster};
+
+mframe_t tank_frames_attack_post_blast [] =	
+{
+	ai_move, 0,		NULL,				// 17
+	ai_move, 0,		NULL,
+	ai_move, 2,		NULL,
+	ai_move, 3,		NULL,
+	ai_move, 2,		NULL,
+	ai_move, -2,	tank_footstep		// 22
+};
+mmove_t tank_move_attack_post_blast = {FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run};
+
+void tank_reattack_blaster (edict_t *self)
+{
+	if (skill->value >= 2)
+		if (visible (self, self->enemy))
+			if (self->enemy->health > 0)
+				if (qrandom() <= 0.6)
+				{
+					self->monsterinfo.currentmove = &tank_move_reattack_blast;
+					return;
+				}
+	self->monsterinfo.currentmove = &tank_move_attack_post_blast;
+}
+
+
+void tank_poststrike (edict_t *self)
+{
+	self->enemy = NULL;
+	tank_run (self);
+}
+
+mframe_t tank_frames_attack_strike [] =
+{
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 6,   NULL,
+	ai_move, 7,   NULL,
+	ai_move, 9,   tank_footstep,
+	ai_move, 2,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 2,   tank_footstep,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 0,   tank_windup,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   TankStrike,
+	ai_move, 0,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -2,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -2,  tank_footstep
+};
+mmove_t tank_move_attack_strike = {FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike};
+
+mframe_t tank_frames_attack_pre_rocket [] =
+{
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 10
+
+	ai_charge, 0,  NULL,
+	ai_charge, 1,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 7,  NULL,
+	ai_charge, 7,  NULL,
+	ai_charge, 7,  tank_footstep,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 20
+
+	ai_charge, -3, NULL
+};
+mmove_t tank_move_attack_pre_rocket = {FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket};
+
+mframe_t tank_frames_attack_fire_rocket [] =
+{
+	ai_charge, -3, NULL,			// Loop Start	22 
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  TankRocket,		// 24
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  TankRocket,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, -1, TankRocket		// 30	Loop End
+};
+mmove_t tank_move_attack_fire_rocket = {FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket};
+
+mframe_t tank_frames_attack_post_rocket [] =
+{	
+	ai_charge, 0,  NULL,			// 31
+	ai_charge, -1, NULL,
+	ai_charge, -1, NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 3,  NULL,
+	ai_charge, 4,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 40
+
+	ai_charge, 0,  NULL,
+	ai_charge, -9, NULL,
+	ai_charge, -8, NULL,
+	ai_charge, -7, NULL,
+	ai_charge, -1, NULL,
+	ai_charge, -1, tank_footstep,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 50
+
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL
+};
+mmove_t tank_move_attack_post_rocket = {FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run};
+
+mframe_t tank_frames_attack_chain [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t tank_move_attack_chain = {FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run};
+
+void tank_refire_rocket (edict_t *self)
+{
+	// PMM - blindfire cleanup
+	if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
+	{
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+		return;
+	}
+	// pmm
+
+	// Only on hard or nightmare
+	if ( skill->value >= 2 )
+		if (self->enemy->health > 0)
+			if (visible(self, self->enemy) )
+				if (qrandom() <= 0.4)
+				{
+					self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+					return;
+				}
+	self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+}
+
+void tank_doattack_rocket (edict_t *self)
+{
+	self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+}
+
+void tank_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+	float	r;
+	// PMM
+	float	chance;
+
+	// PMM
+	if (!self->enemy || !self->enemy->inuse)
+		return;
+
+	if (self->enemy->health < 0)
+	{
+		self->monsterinfo.currentmove = &tank_move_attack_strike;
+		self->monsterinfo.aiflags &= ~AI_BRUTAL;
+		return;
+	}
+
+	// PMM 
+	if (self->monsterinfo.attack_state == AS_BLIND)
+	{
+		// setup shot probabilities
+		if (self->monsterinfo.blind_fire_delay < 1.0)
+			chance = 1.0;
+		else if (self->monsterinfo.blind_fire_delay < 7.5)
+			chance = 0.4;
+		else
+			chance = 0.1;
+
+		r = qrandom();
+
+		self->monsterinfo.blind_fire_delay += 3.2 + 2.0 + qrandom()*3.0;
+
+		// don't shoot at the origin
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+
+		// don't shoot if the dice say not to
+		if (r > chance)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("blindfire - NO SHOT\n");
+			return;
+		}
+
+		// turn on manual steering to signal both manual steering and blindfire
+		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+		self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+		self->monsterinfo.attack_finished = level.time + 3.0 + 2*qrandom();
+		self->pain_debounce_time = level.time + 5.0;	// no pain for a while
+		return;
+	}
+	// pmm
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+
+	r = qrandom();
+
+	if (range <= 125)
+	{
+		if (r < 0.4)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else 
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+	else if (range <= 250)
+	{
+		if (r < 0.5)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+	else
+	{
+		if (r < 0.33)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else if (r < 0.66)
+		{
+			self->monsterinfo.currentmove = &tank_move_attack_pre_rocket;
+			self->pain_debounce_time = level.time + 5.0;	// no pain for a while
+		}
+		else
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+}
+
+
+//
+// death
+//
+
+void tank_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -16);
+	VectorSet (self->maxs, 16, 16, -0);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t tank_frames_death1 [] =
+{
+	ai_move, -7,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 6,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -3,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -4,  NULL,
+	ai_move, -6,  NULL,
+	ai_move, -4,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -7,  NULL,
+	ai_move, -15, tank_thud,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t	tank_move_death = {FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead};
+
+void tank_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 1 /*4*/; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &tank_move_death;
+	
+}
+
+//===========
+//PGM
+qboolean tank_blocked (edict_t *self, float dist)
+{
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+	if(blocked_checkplat (self, dist))
+		return true;
+
+	return false;
+}
+//PGM
+//===========
+
+//
+// monster_tank
+//
+
+/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+*/
+/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_tank (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
+	VectorSet (self->mins, -32, -32, -16);
+	VectorSet (self->maxs, 32, 32, 72);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	sound_pain = gi.soundindex ("tank/tnkpain2.wav");
+	sound_thud = gi.soundindex ("tank/tnkdeth2.wav");
+	sound_idle = gi.soundindex ("tank/tnkidle1.wav");
+	sound_die = gi.soundindex ("tank/death.wav");
+	sound_step = gi.soundindex ("tank/step.wav");
+	sound_windup = gi.soundindex ("tank/tnkatck4.wav");
+	sound_strike = gi.soundindex ("tank/tnkatck5.wav");
+	sound_sight = gi.soundindex ("tank/sight1.wav");
+
+	gi.soundindex ("tank/tnkatck1.wav");
+	gi.soundindex ("tank/tnkatk2a.wav");
+	gi.soundindex ("tank/tnkatk2b.wav");
+	gi.soundindex ("tank/tnkatk2c.wav");
+	gi.soundindex ("tank/tnkatk2d.wav");
+	gi.soundindex ("tank/tnkatk2e.wav");
+	gi.soundindex ("tank/tnkatck3.wav");
+
+	if (strcmp(self->classname, "monster_tank_commander") == 0)
+	{
+		self->health = 1000;
+		self->gib_health = -225;
+	}
+	else
+	{
+		self->health = 750;
+		self->gib_health = -200;
+	}
+
+	self->mass = 500;
+
+	self->pain = tank_pain;
+	self->die = tank_die;
+	self->monsterinfo.stand = tank_stand;
+	self->monsterinfo.walk = tank_walk;
+	self->monsterinfo.run = tank_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = tank_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = tank_sight;
+	self->monsterinfo.idle = tank_idle;
+	self->monsterinfo.blocked = tank_blocked;		// PGM
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &tank_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+
+	// PMM
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+	self->monsterinfo.blindfire = true;
+	//pmm
+	if (strcmp(self->classname, "monster_tank_commander") == 0)
+		self->s.skinnum = 2;
+}
--- /dev/null
+++ b/rogue/m_tank.h
@@ -1,0 +1,300 @@
+// G:\quake2\baseq2\models/monsters/tank
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_walk01          	30
+#define FRAME_walk02          	31
+#define FRAME_walk03          	32
+#define FRAME_walk04          	33
+#define FRAME_walk05          	34
+#define FRAME_walk06          	35
+#define FRAME_walk07          	36
+#define FRAME_walk08          	37
+#define FRAME_walk09          	38
+#define FRAME_walk10          	39
+#define FRAME_walk11          	40
+#define FRAME_walk12          	41
+#define FRAME_walk13          	42
+#define FRAME_walk14          	43
+#define FRAME_walk15          	44
+#define FRAME_walk16          	45
+#define FRAME_walk17          	46
+#define FRAME_walk18          	47
+#define FRAME_walk19          	48
+#define FRAME_walk20          	49
+#define FRAME_walk21          	50
+#define FRAME_walk22          	51
+#define FRAME_walk23          	52
+#define FRAME_walk24          	53
+#define FRAME_walk25          	54
+#define FRAME_attak101        	55
+#define FRAME_attak102        	56
+#define FRAME_attak103        	57
+#define FRAME_attak104        	58
+#define FRAME_attak105        	59
+#define FRAME_attak106        	60
+#define FRAME_attak107        	61
+#define FRAME_attak108        	62
+#define FRAME_attak109        	63
+#define FRAME_attak110        	64
+#define FRAME_attak111        	65
+#define FRAME_attak112        	66
+#define FRAME_attak113        	67
+#define FRAME_attak114        	68
+#define FRAME_attak115        	69
+#define FRAME_attak116        	70
+#define FRAME_attak117        	71
+#define FRAME_attak118        	72
+#define FRAME_attak119        	73
+#define FRAME_attak120        	74
+#define FRAME_attak121        	75
+#define FRAME_attak122        	76
+#define FRAME_attak201        	77
+#define FRAME_attak202        	78
+#define FRAME_attak203        	79
+#define FRAME_attak204        	80
+#define FRAME_attak205        	81
+#define FRAME_attak206        	82
+#define FRAME_attak207        	83
+#define FRAME_attak208        	84
+#define FRAME_attak209        	85
+#define FRAME_attak210        	86
+#define FRAME_attak211        	87
+#define FRAME_attak212        	88
+#define FRAME_attak213        	89
+#define FRAME_attak214        	90
+#define FRAME_attak215        	91
+#define FRAME_attak216        	92
+#define FRAME_attak217        	93
+#define FRAME_attak218        	94
+#define FRAME_attak219        	95
+#define FRAME_attak220        	96
+#define FRAME_attak221        	97
+#define FRAME_attak222        	98
+#define FRAME_attak223        	99
+#define FRAME_attak224        	100
+#define FRAME_attak225        	101
+#define FRAME_attak226        	102
+#define FRAME_attak227        	103
+#define FRAME_attak228        	104
+#define FRAME_attak229        	105
+#define FRAME_attak230        	106
+#define FRAME_attak231        	107
+#define FRAME_attak232        	108
+#define FRAME_attak233        	109
+#define FRAME_attak234        	110
+#define FRAME_attak235        	111
+#define FRAME_attak236        	112
+#define FRAME_attak237        	113
+#define FRAME_attak238        	114
+#define FRAME_attak301        	115
+#define FRAME_attak302        	116
+#define FRAME_attak303        	117
+#define FRAME_attak304        	118
+#define FRAME_attak305        	119
+#define FRAME_attak306        	120
+#define FRAME_attak307        	121
+#define FRAME_attak308        	122
+#define FRAME_attak309        	123
+#define FRAME_attak310        	124
+#define FRAME_attak311        	125
+#define FRAME_attak312        	126
+#define FRAME_attak313        	127
+#define FRAME_attak314        	128
+#define FRAME_attak315        	129
+#define FRAME_attak316        	130
+#define FRAME_attak317        	131
+#define FRAME_attak318        	132
+#define FRAME_attak319        	133
+#define FRAME_attak320        	134
+#define FRAME_attak321        	135
+#define FRAME_attak322        	136
+#define FRAME_attak323        	137
+#define FRAME_attak324        	138
+#define FRAME_attak325        	139
+#define FRAME_attak326        	140
+#define FRAME_attak327        	141
+#define FRAME_attak328        	142
+#define FRAME_attak329        	143
+#define FRAME_attak330        	144
+#define FRAME_attak331        	145
+#define FRAME_attak332        	146
+#define FRAME_attak333        	147
+#define FRAME_attak334        	148
+#define FRAME_attak335        	149
+#define FRAME_attak336        	150
+#define FRAME_attak337        	151
+#define FRAME_attak338        	152
+#define FRAME_attak339        	153
+#define FRAME_attak340        	154
+#define FRAME_attak341        	155
+#define FRAME_attak342        	156
+#define FRAME_attak343        	157
+#define FRAME_attak344        	158
+#define FRAME_attak345        	159
+#define FRAME_attak346        	160
+#define FRAME_attak347        	161
+#define FRAME_attak348        	162
+#define FRAME_attak349        	163
+#define FRAME_attak350        	164
+#define FRAME_attak351        	165
+#define FRAME_attak352        	166
+#define FRAME_attak353        	167
+#define FRAME_attak401        	168
+#define FRAME_attak402        	169
+#define FRAME_attak403        	170
+#define FRAME_attak404        	171
+#define FRAME_attak405        	172
+#define FRAME_attak406        	173
+#define FRAME_attak407        	174
+#define FRAME_attak408        	175
+#define FRAME_attak409        	176
+#define FRAME_attak410        	177
+#define FRAME_attak411        	178
+#define FRAME_attak412        	179
+#define FRAME_attak413        	180
+#define FRAME_attak414        	181
+#define FRAME_attak415        	182
+#define FRAME_attak416        	183
+#define FRAME_attak417        	184
+#define FRAME_attak418        	185
+#define FRAME_attak419        	186
+#define FRAME_attak420        	187
+#define FRAME_attak421        	188
+#define FRAME_attak422        	189
+#define FRAME_attak423        	190
+#define FRAME_attak424        	191
+#define FRAME_attak425        	192
+#define FRAME_attak426        	193
+#define FRAME_attak427        	194
+#define FRAME_attak428        	195
+#define FRAME_attak429        	196
+#define FRAME_pain101         	197
+#define FRAME_pain102         	198
+#define FRAME_pain103         	199
+#define FRAME_pain104         	200
+#define FRAME_pain201         	201
+#define FRAME_pain202         	202
+#define FRAME_pain203         	203
+#define FRAME_pain204         	204
+#define FRAME_pain205         	205
+#define FRAME_pain301         	206
+#define FRAME_pain302         	207
+#define FRAME_pain303         	208
+#define FRAME_pain304         	209
+#define FRAME_pain305         	210
+#define FRAME_pain306         	211
+#define FRAME_pain307         	212
+#define FRAME_pain308         	213
+#define FRAME_pain309         	214
+#define FRAME_pain310         	215
+#define FRAME_pain311         	216
+#define FRAME_pain312         	217
+#define FRAME_pain313         	218
+#define FRAME_pain314         	219
+#define FRAME_pain315         	220
+#define FRAME_pain316         	221
+#define FRAME_death101        	222
+#define FRAME_death102        	223
+#define FRAME_death103        	224
+#define FRAME_death104        	225
+#define FRAME_death105        	226
+#define FRAME_death106        	227
+#define FRAME_death107        	228
+#define FRAME_death108        	229
+#define FRAME_death109        	230
+#define FRAME_death110        	231
+#define FRAME_death111        	232
+#define FRAME_death112        	233
+#define FRAME_death113        	234
+#define FRAME_death114        	235
+#define FRAME_death115        	236
+#define FRAME_death116        	237
+#define FRAME_death117        	238
+#define FRAME_death118        	239
+#define FRAME_death119        	240
+#define FRAME_death120        	241
+#define FRAME_death121        	242
+#define FRAME_death122        	243
+#define FRAME_death123        	244
+#define FRAME_death124        	245
+#define FRAME_death125        	246
+#define FRAME_death126        	247
+#define FRAME_death127        	248
+#define FRAME_death128        	249
+#define FRAME_death129        	250
+#define FRAME_death130        	251
+#define FRAME_death131        	252
+#define FRAME_death132        	253
+#define FRAME_recln101        	254
+#define FRAME_recln102        	255
+#define FRAME_recln103        	256
+#define FRAME_recln104        	257
+#define FRAME_recln105        	258
+#define FRAME_recln106        	259
+#define FRAME_recln107        	260
+#define FRAME_recln108        	261
+#define FRAME_recln109        	262
+#define FRAME_recln110        	263
+#define FRAME_recln111        	264
+#define FRAME_recln112        	265
+#define FRAME_recln113        	266
+#define FRAME_recln114        	267
+#define FRAME_recln115        	268
+#define FRAME_recln116        	269
+#define FRAME_recln117        	270
+#define FRAME_recln118        	271
+#define FRAME_recln119        	272
+#define FRAME_recln120        	273
+#define FRAME_recln121        	274
+#define FRAME_recln122        	275
+#define FRAME_recln123        	276
+#define FRAME_recln124        	277
+#define FRAME_recln125        	278
+#define FRAME_recln126        	279
+#define FRAME_recln127        	280
+#define FRAME_recln128        	281
+#define FRAME_recln129        	282
+#define FRAME_recln130        	283
+#define FRAME_recln131        	284
+#define FRAME_recln132        	285
+#define FRAME_recln133        	286
+#define FRAME_recln134        	287
+#define FRAME_recln135        	288
+#define FRAME_recln136        	289
+#define FRAME_recln137        	290
+#define FRAME_recln138        	291
+#define FRAME_recln139        	292
+#define FRAME_recln140        	293
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/rogue/m_turret.c
@@ -1,0 +1,1033 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_turret.h"
+
+#define SPAWN_BLASTER			0x0008
+#define SPAWN_MACHINEGUN		0x0010
+#define SPAWN_ROCKET			0x0020
+#define SPAWN_HEATBEAM			0x0040
+#define SPAWN_WEAPONCHOICE		0x0078
+#define SPAWN_INSTANT_WEAPON	0x0050
+#define SPAWN_WALL_UNIT			0x0080
+
+extern qboolean FindTarget (edict_t *self);
+
+void turret_run (edict_t *self);
+void TurretAim (edict_t *self);
+void turret_sight (edict_t *self, edict_t *other);
+void turret_search (edict_t *self);
+void turret_stand (edict_t *self);
+void turret_wake (edict_t *self);
+void turret_ready_gun (edict_t *self);
+void turret_run (edict_t *self);
+
+void turret_attack (edict_t *self);
+mmove_t turret_move_fire;
+mmove_t turret_move_fire_blind;
+
+
+void TurretAim(edict_t *self)
+{
+	vec3_t	end, dir;
+	vec3_t	ang;
+	float	move, idealPitch, idealYaw, current, speed;
+	int		orientation;
+
+// gi.dprintf("turret_aim: %d %d\n", self->s.frame, self->monsterinfo.nextframe);
+
+	if(!self->enemy || self->enemy == WORLD)
+	{
+		if(!FindTarget (self))
+			return;
+	}
+
+	// if turret is still in inactive mode, ready the gun, but don't aim
+	if(self->s.frame < FRAME_active01)
+	{
+		turret_ready_gun(self);
+		return;
+	}
+	// if turret is still readying, don't aim.
+	if(self->s.frame < FRAME_run01)
+		return;
+
+	// PMM - blindfire aiming here
+	if (self->monsterinfo.currentmove == &turret_move_fire_blind)
+	{
+		VectorCopy(self->monsterinfo.blind_fire_target, end);
+		if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
+			end[2] += self->enemy->viewheight + 10;
+		else
+			end[2] += self->enemy->mins[2] - 10;
+	}
+	else
+	{
+		VectorCopy(self->enemy->s.origin, end);
+		if (self->enemy->client)
+			end[2] += self->enemy->viewheight;
+	}
+
+	VectorSubtract(end, self->s.origin, dir);
+	vectoangles2(dir, ang);
+
+	//
+	// Clamp first
+	//
+
+	idealPitch = ang[PITCH];
+	idealYaw = ang[YAW];
+
+	orientation = self->offset[1];
+	switch(orientation)
+	{
+		case -1:			// up		pitch: 0 to 90
+			if(idealPitch < -90)
+				idealPitch += 360;
+			if(idealPitch > -5)
+				idealPitch = -5;
+			break;
+		case -2:			// down		pitch: -180 to -360
+			if(idealPitch > -90)
+				idealPitch -= 360;
+			if(idealPitch < -355)
+				idealPitch = -355;
+			else if(idealPitch > -185)
+				idealPitch = -185;
+			break;
+		case 0:				// +X		pitch: 0 to -90, -270 to -360 (or 0 to 90)
+//gi.dprintf("idealpitch %0.1f  idealyaw %0.1f\n", idealPitch, idealYaw);
+			if(idealPitch < -180)
+				idealPitch += 360;
+
+			if(idealPitch > 85)
+				idealPitch = 85;
+			else if(idealPitch < -85)
+				idealPitch = -85;
+
+//gi.dprintf("idealpitch %0.1f  idealyaw %0.1f\n", idealPitch, idealYaw);
+							//			yaw: 270 to 360, 0 to 90
+							//			yaw: -90 to 90 (270-360 == -90-0)
+			if(idealYaw > 180)
+				idealYaw -= 360;
+			if(idealYaw > 85)
+				idealYaw = 85;
+			else if(idealYaw < -85)
+				idealYaw = -85;
+//gi.dprintf("idealpitch %0.1f  idealyaw %0.1f\n", idealPitch, idealYaw);
+			break;
+		case 90:			// +Y	pitch: 0 to 90, -270 to -360 (or 0 to 90)
+			if(idealPitch < -180)
+				idealPitch += 360;
+
+			if(idealPitch > 85)
+				idealPitch = 85;
+			else if(idealPitch < -85)
+				idealPitch = -85;
+
+							//			yaw: 0 to 180
+			if(idealYaw > 270)
+				idealYaw -= 360;
+			if(idealYaw > 175)	idealYaw = 175;
+			else if(idealYaw < 5)	idealYaw = 5;
+
+			break;
+		case 180:			// -X	pitch: 0 to 90, -270 to -360 (or 0 to 90)
+			if(idealPitch < -180)
+				idealPitch += 360;
+
+			if(idealPitch > 85)
+				idealPitch = 85;
+			else if(idealPitch < -85)
+				idealPitch = -85;
+
+							//			yaw: 90 to 270
+			if(idealYaw > 265)	idealYaw = 265;
+			else if(idealYaw < 95)	idealYaw = 95;
+
+			break;
+		case 270:			// -Y	pitch: 0 to 90, -270 to -360 (or 0 to 90)
+			if(idealPitch < -180)
+				idealPitch += 360;
+
+			if(idealPitch > 85)
+				idealPitch = 85;
+			else if(idealPitch < -85)
+				idealPitch = -85;
+
+							//			yaw: 180 to 360
+			if(idealYaw < 90)
+				idealYaw += 360;
+			if(idealYaw > 355)	idealYaw = 355;
+			else if(idealYaw < 185)	idealYaw = 185;
+			break;
+	}
+
+	//
+	// adjust pitch
+	//
+	current = self->s.angles[PITCH];
+	speed = self->yaw_speed;
+
+	if(idealPitch != current)
+	{
+		move = idealPitch - current;
+
+		while(move >= 360)
+			move -= 360;
+		if (move >= 90)
+		{
+			move = move - 360;
+		}
+
+		while(move <= -360)
+			move += 360;
+		if (move <= -90)
+		{
+			move = move + 360;
+		}
+
+		if (move > 0)
+		{
+			if (move > speed)
+				move = speed;
+		}
+		else
+		{
+			if (move < -speed)
+				move = -speed;
+		}
+
+		self->s.angles[PITCH] = anglemod (current + move);
+	}
+
+	//
+	// adjust yaw
+	//
+	current = self->s.angles[YAW];
+	speed = self->yaw_speed;
+
+	if(idealYaw != current)
+	{
+		move = idealYaw - current;
+
+//		while(move >= 360)
+//			move -= 360;
+		if (move >= 180)
+		{
+			move = move - 360;
+		}
+
+//		while(move <= -360)
+//			move += 360;
+		if (move <= -180)
+		{
+			move = move + 360;
+		}
+
+		if (move > 0)
+		{
+			if (move > speed)
+				move = speed;
+		}
+		else
+		{
+			if (move < -speed)
+				move = -speed;
+		}
+
+		self->s.angles[YAW] = anglemod (current + move);
+	}
+
+}
+
+void turret_sight (edict_t *, edict_t *)
+{
+}
+
+void turret_search (edict_t *)
+{
+}
+
+mframe_t turret_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t turret_move_stand = {FRAME_stand01, FRAME_stand02, turret_frames_stand, NULL};
+
+void turret_stand (edict_t *self)
+{
+//gi.dprintf("turret_stand\n");
+	self->monsterinfo.currentmove = &turret_move_stand;
+}
+
+mframe_t turret_frames_ready_gun [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	
+	ai_stand, 0, NULL
+};
+mmove_t turret_move_ready_gun = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run };
+
+void turret_ready_gun (edict_t *self)
+{
+	self->monsterinfo.currentmove = &turret_move_ready_gun;
+}
+
+mframe_t turret_frames_seek [] =
+{
+	ai_walk, 0, TurretAim,
+	ai_walk, 0, TurretAim
+};
+mmove_t turret_move_seek = {FRAME_run01, FRAME_run02, turret_frames_seek, NULL};
+
+void turret_walk (edict_t *self)
+{
+	if(self->s.frame < FRAME_run01)
+		turret_ready_gun(self);
+	else
+		self->monsterinfo.currentmove = &turret_move_seek;
+}
+
+
+mframe_t turret_frames_run [] =
+{
+	ai_run, 0, TurretAim,
+	ai_run, 0, TurretAim
+};
+mmove_t turret_move_run = {FRAME_run01, FRAME_run02, turret_frames_run, turret_run};
+
+void turret_run (edict_t *self)
+{
+	if(self->s.frame < FRAME_run01)
+		turret_ready_gun(self);
+	else
+		self->monsterinfo.currentmove = &turret_move_run;
+}
+
+// **********************
+//  ATTACK
+// **********************
+
+#define TURRET_BULLET_DAMAGE	4
+#define TURRET_HEAT_DAMAGE		4
+
+void TurretFire (edict_t *self)
+{
+	vec3_t	forward;
+	vec3_t	start, end, dir;
+	float	time, dist, chance;
+	trace_t	trace;
+	int		rocketSpeed = 0;
+
+	TurretAim(self);
+
+	if(!self->enemy || !self->enemy->inuse)
+		return;
+
+	VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
+	VectorNormalize(dir);
+	AngleVectors(self->s.angles, forward, NULL, NULL);
+	chance = DotProduct(dir, forward);
+	if(chance < 0.98)
+	{
+//		gi.dprintf("off-angle\n");
+		return;
+	}
+
+	//chance = qrandom();
+
+	// rockets fire less often than the others do.
+	if (self->spawnflags & SPAWN_ROCKET)
+	{
+		//chance = chance * 3;
+
+		rocketSpeed = 550;
+		if (skill->value == 2)
+		{
+			rocketSpeed += 200 * qrandom();
+		}
+		else if (skill->value == 3)
+		{
+			rocketSpeed += 100 + (200 * qrandom());
+		}
+	}
+	else if (self->spawnflags & SPAWN_BLASTER)
+	{
+		if (skill->value == 0)
+			rocketSpeed = 600;
+		else if (skill->value == 1)
+			rocketSpeed = 800;
+		else
+			rocketSpeed = 1000;
+		//chance = chance * 2;
+	}
+	
+	// up the fire chance 20% per skill level.
+	//chance = chance - (0.2 * skill->value);
+
+	if(/*chance < 0.5 && */visible(self, self->enemy))
+	{
+		VectorCopy(self->s.origin, start);
+		VectorCopy(self->enemy->s.origin, end);
+		
+		// aim for the head.
+		if ((self->enemy) && (self->enemy->client))
+			end[2]+=self->enemy->viewheight;
+		else
+			end[2]+=22;
+
+		VectorSubtract(end, start, dir);
+		dist = VectorLength(dir);
+		
+		// check for predictive fire if distance less than 512
+		if(!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512))
+		{
+			chance = qrandom();
+			// ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80%
+			chance += (3 - skill->value) * 0.1;
+			if(chance < 0.8)
+			{
+				// lead the target....
+				time = dist / 1000;
+				VectorMA(end, time, self->enemy->velocity, end);
+				VectorSubtract(end, start, dir);
+			}
+		}
+
+		VectorNormalize(dir);
+		trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+		if(trace.ent == self->enemy || trace.ent == WORLD)
+		{
+			if(self->spawnflags & SPAWN_BLASTER)
+				monster_fire_blaster(self, start, dir, 20, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER);
+			else if(self->spawnflags & SPAWN_MACHINEGUN)
+				monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN);
+			else if(self->spawnflags & SPAWN_ROCKET)
+			{
+				if(dist * trace.fraction > 72)
+					monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
+			}
+		}	
+	}
+}
+
+// PMM
+void TurretFireBlind (edict_t *self)
+{
+	vec3_t	forward;
+	vec3_t	start, end, dir;
+	float	chance;
+	int		rocketSpeed = 0;
+
+	TurretAim(self);
+
+	if(!self->enemy || !self->enemy->inuse)
+		return;
+
+	VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir);
+	VectorNormalize(dir);
+	AngleVectors(self->s.angles, forward, NULL, NULL);
+	chance = DotProduct(dir, forward);
+	if(chance < 0.98)
+	{
+//		gi.dprintf("off-angle\n");
+		return;
+	}
+
+	if (self->spawnflags & SPAWN_ROCKET)
+	{
+		rocketSpeed = 550;
+		if (skill->value == 2)
+		{
+			rocketSpeed += 200 * qrandom();
+		}
+		else if (skill->value == 3)
+		{
+			rocketSpeed += 100 + (200 * qrandom());
+		}
+	}
+
+	VectorCopy(self->s.origin, start);
+	VectorCopy(self->monsterinfo.blind_fire_target, end);
+		
+	if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
+		end[2] += self->enemy->viewheight + 10;
+	else
+		end[2] += self->enemy->mins[2] - 10;
+
+	VectorSubtract(end, start, dir);
+		
+	VectorNormalize(dir);
+
+	if(self->spawnflags & SPAWN_BLASTER)
+		monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER);
+	else if(self->spawnflags & SPAWN_ROCKET)
+		monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
+}
+//pmm
+
+mframe_t turret_frames_fire [] =
+{
+	ai_run,   0, TurretFire,
+	ai_run,   0, TurretAim,
+	ai_run,   0, TurretAim,
+	ai_run,   0, TurretAim
+};
+mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run};
+
+//PMM
+
+// the blind frames need to aim first
+mframe_t turret_frames_fire_blind [] =
+{
+	ai_run,   0, TurretAim,
+	ai_run,   0, TurretAim,
+	ai_run,   0, TurretAim,
+	ai_run,   0, TurretFireBlind
+};
+mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run};
+//pmm
+
+void turret_attack(edict_t *self)
+{
+	float r, chance;
+
+	if(self->s.frame < FRAME_run01)
+		turret_ready_gun(self);
+	// PMM
+	else if (self->monsterinfo.attack_state != AS_BLIND)
+	{
+		self->monsterinfo.nextframe = FRAME_pow01;
+		self->monsterinfo.currentmove = &turret_move_fire;
+	}
+	else
+	{
+		// setup shot probabilities
+		if (self->monsterinfo.blind_fire_delay < 1.0)
+			chance = 1.0;
+		else if (self->monsterinfo.blind_fire_delay < 7.5)
+			chance = 0.4;
+		else
+			chance = 0.1;
+
+		r = qrandom();
+
+		// minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5
+		self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + qrandom()*4.0;
+		// don't shoot at the origin
+		if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
+			return;
+
+		// don't shoot if the dice say not to
+		if (r > chance)
+			return;
+
+		self->monsterinfo.nextframe = FRAME_pow01;
+		self->monsterinfo.currentmove = &turret_move_fire_blind;
+	}
+	// pmm
+}
+
+// **********************
+//  PAIN
+// **********************
+
+void turret_pain (edict_t *, edict_t *, float, int)
+{
+	return;
+}
+
+// **********************
+//  DEATH
+// **********************
+
+void turret_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	vec3_t		forward;
+	vec3_t		start;
+	edict_t		*base;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PLAIN_EXPLOSION);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+
+	AngleVectors(self->s.angles, forward, NULL, NULL);
+	VectorMA(self->s.origin, 1, forward, start);
+
+	ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
+	ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
+	ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
+	ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
+
+	if(self->teamchain)
+	{
+		base = self->teamchain;
+		base->solid = SOLID_BBOX;
+		base->takedamage = DAMAGE_NO;
+		base->movetype = MOVETYPE_NONE;
+		gi.linkentity (base);
+	}
+
+	if(self->target)
+	{
+		if(self->enemy && self->enemy->inuse)
+			G_UseTargets (self, self->enemy);
+		else
+			G_UseTargets (self, self);
+	}
+
+	G_FreeEdict(self);
+}
+
+// **********************
+//  WALL SPAWN
+// **********************
+
+void turret_wall_spawn (edict_t *turret)
+{
+	edict_t		*ent;
+	int			angle;
+
+	ent = G_Spawn();
+	VectorCopy(turret->s.origin, ent->s.origin);
+	VectorCopy(turret->s.angles, ent->s.angles);
+	
+	angle = ent->s.angles[1];
+	if(ent->s.angles[0] == 90)
+		angle = -1;
+	else if(ent->s.angles[0] == 270)
+		angle = -2;
+	switch (angle)
+	{
+		case -1:
+			VectorSet(ent->mins, -16, -16, -8);
+			VectorSet(ent->maxs, 16, 16, 0);
+			break;
+		case -2:
+			VectorSet(ent->mins, -16, -16, 0);
+			VectorSet(ent->maxs, 16, 16, 8);
+			break;
+		case 0:
+			VectorSet(ent->mins, -8, -16, -16);
+			VectorSet(ent->maxs, 0, 16, 16);
+			break;
+		case 90:
+			VectorSet(ent->mins, -16, -8, -16);
+			VectorSet(ent->maxs, 16, 0, 16);
+			break;
+		case 180:
+			VectorSet(ent->mins, 0, -16, -16);
+			VectorSet(ent->maxs, 8, 16, 16);
+			break;
+		case 270:
+			VectorSet(ent->mins, -16, 0, -16);
+			VectorSet(ent->maxs, 16, 8, 16);
+			break;
+
+	}
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+
+	ent->teammaster = turret;
+	turret->teammaster = turret;
+	turret->teamchain = ent;
+	ent->teamchain = NULL;
+	ent->flags |= FL_TEAMSLAVE;
+	ent->owner = turret;
+
+	ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2");
+
+	gi.linkentity (ent);
+}
+
+void turret_wake (edict_t *self)
+{
+	// the wall section will call this when it stops moving.
+	// just return without doing anything. easiest way to have a null function.
+	if(self->flags & FL_TEAMSLAVE)
+	{
+		return;
+	}
+
+	self->monsterinfo.stand = turret_stand;
+	self->monsterinfo.walk = turret_walk;
+	self->monsterinfo.run = turret_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = turret_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = turret_sight;
+	self->monsterinfo.search = turret_search;
+	self->monsterinfo.currentmove = &turret_move_stand;
+	self->takedamage = DAMAGE_AIM;
+	self->movetype = MOVETYPE_NONE;
+	// prevent counting twice
+	self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
+
+	gi.linkentity (self);
+
+	stationarymonster_start (self);
+	
+	if(self->spawnflags & SPAWN_MACHINEGUN)
+	{
+		self->s.skinnum = 1;
+	}
+	else if(self->spawnflags & SPAWN_ROCKET)
+	{
+		self->s.skinnum = 2;
+	}
+
+	// but we do want the death to count
+	self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT;
+}
+
+extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
+
+void turret_activate (edict_t *self, edict_t *, edict_t *)
+{
+	vec3_t		endpos;
+	vec3_t		forward;
+	edict_t		*base;
+
+	self->movetype = MOVETYPE_PUSH;
+	if(!self->speed)
+		self->speed = 15;
+	self->moveinfo.speed = self->speed;
+	self->moveinfo.accel = self->speed;
+	self->moveinfo.decel = self->speed;
+
+	if(self->s.angles[0] == 270)
+	{
+		VectorSet (forward, 0,0,1);
+	}
+	else if(self->s.angles[0] == 90)
+	{
+		VectorSet (forward, 0,0,-1);
+	}
+	else if(self->s.angles[1] == 0)
+	{
+		VectorSet (forward, 1,0,0);
+	}
+	else if(self->s.angles[1] == 90)
+	{
+		VectorSet (forward, 0,1,0);
+	}
+	else if(self->s.angles[1] == 180)
+	{
+		VectorSet (forward, -1,0,0);
+	}
+	else if(self->s.angles[1] == 270)
+	{
+		VectorSet (forward, 0,-1,0);
+	}
+	
+	// start up the turret
+	VectorMA(self->s.origin, 32, forward, endpos);
+	Move_Calc(self, endpos, turret_wake);
+
+	base = self->teamchain;
+	if(base)
+	{
+		base->movetype = MOVETYPE_PUSH;
+		base->speed = self->speed;
+		base->moveinfo.speed = base->speed;
+		base->moveinfo.accel = base->speed;
+		base->moveinfo.decel = base->speed;
+
+		// start up the wall section
+		VectorMA(self->teamchain->s.origin, 32, forward, endpos);
+		Move_Calc(self->teamchain, endpos, turret_wake);
+	}
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0);
+}
+
+// PMM
+// checkattack .. ignore range, just attack if available
+qboolean turret_checkattack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	float	chance, nexttime;
+	trace_t	tr;
+	int		enemy_range;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+			{
+				// PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
+				if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
+				{
+					if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0))
+					{
+						if (level.time < self->monsterinfo.attack_finished)
+						{
+							return false;
+						}
+						if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
+						{
+							// wait for our time
+							return false;
+						}
+						else
+						{
+							// make sure we're not going to shoot something we don't want to shoot
+							tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
+							if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
+							{
+								return false;
+							}
+
+							self->monsterinfo.attack_state = AS_BLIND;
+							self->monsterinfo.attack_finished = level.time + 0.5 + 2*qrandom();
+							return true;
+						}
+					}
+				}
+				// pmm
+				return false;
+			}
+		}
+	}
+	
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+
+	enemy_range = range(self, self->enemy);
+
+	if (enemy_range == RANGE_MELEE)
+	{
+		// don't always melee in easy mode
+		if (skill->value == 0 && (rand()&3) )
+			return false;
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+	if (self->spawnflags & SPAWN_ROCKET)
+	{
+		chance = 0.10;
+		nexttime = (1.8 - (0.2 * skill->value));
+	}
+	else if(self->spawnflags & SPAWN_BLASTER)
+	{
+		chance = 0.35;
+		nexttime = (1.2 - (0.2 * skill->value));
+	}
+	else
+	{
+		chance = 0.50;
+		nexttime = (0.8 - (0.1 * skill->value));
+	}
+
+	if (skill->value == 0)
+		chance *= 0.5;
+	else if (skill->value > 1)
+		chance *= 2;
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	// PMM - added visibility check
+	if ( ((qrandom () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+//		self->monsterinfo.attack_finished = level.time + 0.3 + 2*random();
+		self->monsterinfo.attack_finished = level.time + nexttime;
+		return true;
+	}
+
+	self->monsterinfo.attack_state = AS_STRAIGHT;
+
+	return false;
+}
+
+
+// **********************
+//  SPAWN
+// **********************
+
+/*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit
+
+The automated defense turret that mounts on walls. 
+Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam.
+Default weapon is blaster.
+When activated, wall units move 32 units in the direction they're facing.
+*/
+void SP_monster_turret (edict_t *self)
+{
+	int		angle;
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// VERSIONING
+//	if (g_showlogic && g_showlogic->value)
+//		gi.dprintf ("%s\n", ROGUE_VERSION_STRING);
+
+//	self->plat2flags = ROGUE_VERSION_ID;
+	// versions
+
+	// pre-caches
+	gi.soundindex ("world/dr_short.wav");
+	gi.modelindex ("models/objects/debris1/tris.md2");
+
+	self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");
+
+	VectorSet (self->mins, -12, -12, -12);
+	VectorSet (self->maxs, 12, 12, 12);
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_BBOX;
+
+	self->health = 240;
+	self->gib_health = -100;
+	self->mass = 250;
+	self->yaw_speed = 45;
+
+	self->flags |= FL_MECHANICAL;
+
+	self->pain = turret_pain;
+	self->die = turret_die;
+
+	// map designer didn't specify weapon type. set it now.
+	if(!(self->spawnflags & SPAWN_WEAPONCHOICE))
+	{
+		self->spawnflags |= SPAWN_BLASTER;
+//		self->spawnflags |= SPAWN_MACHINEGUN;
+//		self->spawnflags |= SPAWN_ROCKET;
+//		self->spawnflags |= SPAWN_HEATBEAM;
+	}
+
+	if(self->spawnflags & SPAWN_HEATBEAM)
+	{
+		self->spawnflags &= ~SPAWN_HEATBEAM;
+		self->spawnflags |= SPAWN_BLASTER;
+	}
+
+	if(!(self->spawnflags & SPAWN_WALL_UNIT))
+	{
+		self->monsterinfo.stand = turret_stand;
+		self->monsterinfo.walk = turret_walk;
+		self->monsterinfo.run = turret_run;
+		self->monsterinfo.dodge = NULL;
+		self->monsterinfo.attack = turret_attack;
+		self->monsterinfo.melee = NULL;
+		self->monsterinfo.sight = turret_sight;
+		self->monsterinfo.search = turret_search;
+		self->monsterinfo.currentmove = &turret_move_stand;
+	}
+
+	// PMM
+	self->monsterinfo.checkattack = turret_checkattack;
+
+	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+	self->monsterinfo.scale = MODEL_SCALE;
+	self->gravity = 0;
+
+	VectorCopy(self->s.angles, self->offset);
+	angle=(int)self->s.angles[1];
+	switch(angle)
+	{
+		case -1:					// up
+			self->s.angles[0] = 270;
+			self->s.angles[1] = 0;
+			self->s.origin[2] += 2;
+			break;
+		case -2:					// down
+			self->s.angles[0] = 90;
+			self->s.angles[1] = 0;
+			self->s.origin[2] -= 2;
+			break;
+		case 0:
+			self->s.origin[0] += 2;
+			break;
+		case 90:
+			self->s.origin[1] += 2;
+			break;
+		case 180:
+			self->s.origin[0] -= 2;
+			break;
+		case 270:
+			self->s.origin[1] -= 2;
+			break;
+		default:
+			break;
+	}
+
+	gi.linkentity (self);
+
+
+	if(self->spawnflags & SPAWN_WALL_UNIT)
+	{
+		if(!self->targetname)
+		{
+//			gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin));
+			G_FreeEdict(self);
+			return;
+		}
+
+		self->takedamage = DAMAGE_NO;
+		self->use = turret_activate;
+		turret_wall_spawn(self);
+		if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
+			level.total_monsters++;
+
+	}
+	else
+	{
+		stationarymonster_start (self);
+	}
+
+	if(self->spawnflags & SPAWN_MACHINEGUN)
+	{
+		gi.soundindex ("infantry/infatck1.wav");
+		self->s.skinnum = 1;
+	}
+	else if(self->spawnflags & SPAWN_ROCKET)
+	{
+		gi.soundindex ("weapons/rockfly.wav");
+		gi.modelindex ("models/objects/rocket/tris.md2");
+		gi.soundindex ("chick/chkatck2.wav");
+		self->s.skinnum = 2;
+	}
+	else
+	{
+		if (!(self->spawnflags & SPAWN_BLASTER))
+		{
+			self->spawnflags |= SPAWN_BLASTER;
+		}
+		gi.modelindex ("models/objects/laser/tris.md2");
+		gi.soundindex ("misc/lasfly.wav");
+		gi.soundindex ("soldier/solatck2.wav");
+	}
+	
+	// PMM  - turrets don't get mad at monsters, and visa versa
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+	// PMM - blindfire
+	if(self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER))
+		self->monsterinfo.blindfire = true;
+}
--- /dev/null
+++ b/rogue/m_turret.h
@@ -1,0 +1,22 @@
+// G:\quake2\xpack\models/monsters/turret
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_active01        	2
+#define FRAME_active02        	3
+#define FRAME_active03        	4
+#define FRAME_active04        	5
+#define FRAME_active05        	6
+#define FRAME_active06        	7
+#define FRAME_run01           	8
+#define FRAME_run02           	9
+#define FRAME_pow01           	10
+#define FRAME_pow02           	11
+#define FRAME_pow03           	12
+#define FRAME_pow04           	13
+#define FRAME_death01         	14
+#define FRAME_death02         	15
+
+#define MODEL_SCALE		3.500000
--- /dev/null
+++ b/rogue/m_widow.c
@@ -1,0 +1,1708 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_widow.h"
+
+// self->timestamp used to prevent rapid fire of railgun
+// self->plat2flags used for fire count (flashes)
+// self->monsterinfo.pausetime used for timing of blaster shots
+
+#define	NUM_STALKERS_SPAWNED		6		// max # of stalkers she can spawn
+
+#define	RAIL_TIME					3
+#define	BLASTER_TIME				2
+#define	BLASTER2_DAMAGE				10
+#define	WIDOW_RAIL_DAMAGE			50
+
+#define	DRAWBBOX					NULL
+#define	SHOWME						NULL	// showme
+
+void BossExplode (edict_t *self);
+
+qboolean infront (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_search1;
+static int	sound_rail;
+static int	sound_sight;
+
+static unsigned long shotsfired;
+
+static vec3_t spawnpoints[] = {
+	{30,  100, 16},
+	{30, -100, 16}
+};
+
+static vec3_t beameffects[] = {
+	{12.58, -43.71, 68.88},
+	{3.43, 58.72, 68.41}
+};
+
+static float sweep_angles[] = {
+//	32.0, 26.0, 20.0, 11.5, 3.0, -8.0, -13.0, -27.0, -41.0
+	32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0
+};
+
+vec3_t stalker_mins = {-28, -28, -18};
+vec3_t stalker_maxs = {28, 28, 18};
+
+unsigned int	widow_damage_multiplier;
+
+void widow_run (edict_t *self);
+void widow_stand (edict_t *self);
+void widow_dead (edict_t *self);
+void widow_attack (edict_t *self);
+void widow_attack_blaster (edict_t *self);
+void widow_reattack_blaster (edict_t *self);
+void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+void widow_start_spawn (edict_t *self);
+void widow_done_spawn (edict_t *self);
+void widow_spawn_check (edict_t *self);
+void widow_prep_spawn (edict_t *self);
+void widow_attack_rail (edict_t *self);
+
+void widow_start_run_5 (edict_t *self);
+void widow_start_run_10 (edict_t *self);
+void widow_start_run_12 (edict_t *self);
+
+void WidowCalcSlots (edict_t *self);
+
+void drawbbox (edict_t *self);
+
+void showme (edict_t *self)
+{
+	gi.dprintf ("frame %d\n", self->s.frame);
+}
+
+void widow_search (edict_t *)
+{
+//	if (qrandom() < 0.5)
+//		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+}
+
+void widow_sight (edict_t *self, edict_t *)
+{
+	self->monsterinfo.pausetime = 0;
+//	gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("widow: found target!\n");
+}
+
+mmove_t widow_move_attack_post_blaster;
+mmove_t widow_move_attack_post_blaster_r;
+mmove_t widow_move_attack_post_blaster_l;
+mmove_t widow_move_attack_blaster;
+
+float target_angle (edict_t *self)
+{
+	vec3_t target;
+	float enemy_yaw;
+
+	VectorSubtract (self->s.origin, self->enemy->s.origin, target);
+	enemy_yaw = self->s.angles[YAW] - vectoyaw2(target);
+	if (enemy_yaw < 0)
+		enemy_yaw += 360.0;
+
+	// this gets me 0 degrees = forward
+	enemy_yaw -= 180.0;
+	// positive is to right, negative to left
+
+	return enemy_yaw;
+}
+
+int WidowTorso (edict_t *self)
+{
+	float enemy_yaw;
+
+	enemy_yaw = target_angle (self);
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("%2.2f -> ", enemy_yaw);
+
+	if (enemy_yaw >= 105)
+	{
+		self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r;
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		return 0;
+	}
+
+	if (enemy_yaw <= -75.0)
+	{
+		self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l;
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		return 0;
+	}
+
+	if (enemy_yaw >= 95)
+		return FRAME_fired03;
+	else if (enemy_yaw >= 85)
+		return FRAME_fired04;
+	else if (enemy_yaw >= 75)
+		return FRAME_fired05;
+	else if (enemy_yaw >= 65)
+		return FRAME_fired06;
+	else if (enemy_yaw >= 55)
+		return FRAME_fired07;
+	else if (enemy_yaw >= 45)
+		return FRAME_fired08;
+	else if (enemy_yaw >= 35)
+		return FRAME_fired09;
+	else if (enemy_yaw >= 25)
+		return FRAME_fired10;
+	else if (enemy_yaw >= 15)
+		return FRAME_fired11;
+	else if (enemy_yaw >= 5)
+		return FRAME_fired12;
+	else if (enemy_yaw >= -5)
+		return FRAME_fired13;
+	else if (enemy_yaw >= -15)
+		return FRAME_fired14;
+	else if (enemy_yaw >= -25)
+		return FRAME_fired15;
+	else if (enemy_yaw >= -35)
+		return FRAME_fired16;
+	else if (enemy_yaw >= -45)
+		return FRAME_fired17;
+	else if (enemy_yaw >= -55)
+		return FRAME_fired18;
+	else if (enemy_yaw >= -65)
+		return FRAME_fired19;
+	else if (enemy_yaw >= -75)
+		return FRAME_fired20;
+/*
+	if (fabs(enemy_yaw) < 11.25)
+		return FRAME_fired03;
+	else if (fabs(enemy_yaw) > 56.25)
+	{
+		self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
+		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		return;
+	}
+	else if ((enemy_yaw >= 11.25) && (enemy_yaw < 33.75))
+		return FRAME_fired04;
+	else if (enemy_yaw >= 33.75)
+		return FRAME_fired05;
+	else if ((enemy_yaw <= -11.25) && (enemy_yaw > -33.75))
+		return FRAME_fired06;
+	else if (enemy_yaw <= -33.75)
+		return FRAME_fired07;
+*/
+	return 0;
+}
+
+#define	VARIANCE 15.0
+
+void WidowBlaster (edict_t *self)
+{
+	vec3_t	forward, right, target, vec, targ_angles;
+	vec3_t	start;
+	int		flashnum;
+	int		effect;
+
+	if (!self->enemy)
+		return;
+
+	shotsfired++;
+	if (!(shotsfired % 4))
+		effect = EF_BLASTER;
+	else
+		effect = 0;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13))
+	{
+		// sweep
+		flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05;
+		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+		VectorSubtract (self->enemy->s.origin, start, target);
+		vectoangles2 (target, targ_angles);
+		
+		VectorCopy (self->s.angles, vec);
+
+		vec[PITCH] += targ_angles[PITCH];
+		vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW_BLASTER_SWEEP1];
+
+		AngleVectors (vec, forward, NULL, NULL);
+		monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
+
+/*		if (self->s.frame == FRAME_spawn13)
+		{
+			VectorMA (start, 1024, forward, debugend);
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (start);
+			gi.WritePosition (debugend);
+			gi.multicast (start, MULTICAST_ALL);
+
+			drawbbox (self);
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING;
+		}
+*/
+	}
+	else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20))
+	{
+		vec3_t angles;
+		float aim_angle, target_angle;
+		float error;
+
+		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+
+		self->monsterinfo.nextframe = WidowTorso (self);
+
+		if (!self->monsterinfo.nextframe)
+			self->monsterinfo.nextframe = self->s.frame;
+
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("%d\n", self->monsterinfo.nextframe);
+
+		if (self->s.frame == FRAME_fired02a)
+			flashnum = MZ2_WIDOW_BLASTER_0;
+		else
+			flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03;
+
+		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+
+		PredictAim (self->enemy, start, 1000, true, ((qrandom()*0.1)-0.05), forward, NULL);
+
+		// clamp it to within 10 degrees of the aiming angle (where she's facing)
+		vectoangles2 (forward, angles);
+		// give me 100 -> -70
+		aim_angle = 100 - (10*(flashnum-MZ2_WIDOW_BLASTER_100));
+		if (aim_angle <= 0)
+			aim_angle += 360;
+		target_angle = self->s.angles[YAW] - angles[YAW];
+		if (target_angle <= 0)
+			target_angle += 360;
+
+		error = aim_angle - target_angle;
+
+		// positive error is to entity's left, aka positive direction in engine
+		// unfortunately, I decided that for the aim_angle, positive was right.  *sigh*
+		if (error > VARIANCE)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
+			angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE;
+//			if ((g_showlogic) && (g_showlogic->value))
+//			{
+//				if (angles[YAW] <= 0)
+//					angles[YAW] += 360;
+//				gi.dprintf (" %2.2f\n", angles[YAW]);
+//			}
+			AngleVectors (angles, forward, NULL, NULL);
+		}
+		else if (error < -VARIANCE)
+		{
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
+			angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE;
+//			if ((g_showlogic) && (g_showlogic->value))
+//			{
+//				if (angles[YAW] <= 0)
+//					angles[YAW] += 360;
+//				gi.dprintf (" %2.2f\n", angles[YAW]);
+//			}
+			AngleVectors (angles, forward, NULL, NULL);
+		}
+//		gi.dprintf ("%2.2f - %2.2f - %2.2f - %2.2f\n", aim_angle, self->s.angles[YAW] - angles[YAW], target_angle, error);
+//		gi.dprintf ("%2.2f - %2.2f - %2.2f\n", angles[YAW], aim_angle, self->s.angles[YAW]);
+
+/*
+		if (self->s.frame == FRAME_fired20)
+		{
+			VectorMA (start, 512, forward, debugend);
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (start);
+			gi.WritePosition (forward);
+			gi.multicast (start, MULTICAST_ALL);
+
+			drawbbox (self);
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+			self->monsterinfo.nextframe = FRAME_fired20;
+			self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+		}
+*/
+/*
+		if (!(self->plat2flags % 3))
+			effect = EF_HYPERBLASTER;
+		else
+			effect = 0;
+		self->plat2flags ++;
+*/
+		monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
+	}
+	else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08))
+	{
+		flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01;
+		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+		
+		VectorSubtract (self->enemy->s.origin, start, target);
+		target[2] += self->enemy->viewheight;
+
+		monster_fire_blaster2 (self, start, target, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
+	}
+//	else
+//	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("widow: firing on non-fire frame!\n");
+//	}
+}	
+
+void WidowSpawn (edict_t *self)
+{
+	vec3_t	f, r, u, offset, startpoint, spawnpoint;
+	edict_t	*ent, *designated_enemy;
+	int		i;
+
+	AngleVectors (self->s.angles, f, r, u);
+
+	for (i=0; i < 2; i++)
+	{
+		VectorCopy (spawnpoints[i], offset);
+
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+		{
+			ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256);
+
+			if (!ent)
+				continue;
+			
+			self->monsterinfo.monster_used++;
+			ent->monsterinfo.commander = self;
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("widow: post-spawn : %d slots left out of %d\n", SELF_SLOTS_LEFT, self->monsterinfo.monster_slots);
+
+			ent->nextthink = level.time;
+			ent->think (ent);
+			
+			ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
+
+			if (!(coop && coop->value))
+			{
+				designated_enemy = self->enemy;
+			}
+			else
+			{
+				designated_enemy = PickCoopTarget(ent);
+				if (designated_enemy)
+				{
+					// try to avoid using my enemy
+					if (designated_enemy == self->enemy)
+					{
+						designated_enemy = PickCoopTarget(ent);
+						if (designated_enemy)
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//							{
+//								gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
+//								if (designated_enemy->client)
+//									gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
+//								else
+//									gi.dprintf ("NOT A CLIENT\n");
+//							}
+						}
+						else
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//								gi.dprintf ("pick coop failed, using my current enemy\n");
+							designated_enemy = self->enemy;
+						}
+					}
+				}
+				else
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("pick coop failed, using my current enemy\n");
+					designated_enemy = self->enemy;
+				}
+			}
+
+			if ((designated_enemy->inuse) && (designated_enemy->health > 0))
+			{
+				ent->enemy = designated_enemy;
+				FoundTarget (ent);
+				ent->monsterinfo.attack(ent);
+			}
+		}
+	}
+}
+
+void widow_spawn_check (edict_t *self)
+{
+	WidowBlaster(self);
+	WidowSpawn (self);
+}
+
+void widow_ready_spawn (edict_t *self)
+{
+	vec3_t	f, r, u, offset, startpoint, spawnpoint;
+	int		i;
+
+	WidowBlaster(self);
+	AngleVectors (self->s.angles, f, r, u);
+
+	for (i=0; i < 2; i++)
+	{
+		VectorCopy (spawnpoints[i], offset);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+		{
+			SpawnGrow_Spawn (spawnpoint, 1);
+		}
+	}
+}
+
+void widow_step (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0);
+}
+
+mframe_t widow_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	widow_move_stand = {FRAME_idle01, FRAME_idle11, widow_frames_stand, NULL};
+
+mframe_t widow_frames_walk [] =
+{
+	// hand generated numbers
+/*
+	ai_run,	6,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	4,	NULL,			//5
+	ai_run,	4,	NULL,
+	ai_run,	4,	NULL,
+	ai_run,	4.5,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	5,	NULL,			//10
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	6.5,	NULL
+*/
+	// auto generated numbers
+	ai_walk,	2.79,	widow_step,
+	ai_walk,	2.77,	NULL,
+	ai_walk,	3.53,	NULL,
+	ai_walk,	3.97,	NULL,
+	ai_walk,	4.13,	NULL,			//5
+	ai_walk,	4.09,	NULL,
+	ai_walk,	3.84,	NULL,
+	ai_walk,	3.62,	widow_step,
+	ai_walk,	3.29,	NULL,
+	ai_walk,	6.08,	NULL,			//10
+	ai_walk,	6.94,	NULL,
+	ai_walk,	5.73,	NULL,
+	ai_walk,	2.85,	NULL
+};
+mmove_t widow_move_walk = {FRAME_walk01, FRAME_walk13, widow_frames_walk, NULL};
+
+
+mframe_t widow_frames_run [] =
+{
+	ai_run,	2.79,	widow_step,
+	ai_run,	2.77,	NULL,
+	ai_run,	3.53,	NULL,
+	ai_run,	3.97,	NULL,
+	ai_run,	4.13,	NULL,			//5
+	ai_run,	4.09,	NULL,
+	ai_run,	3.84,	NULL,
+	ai_run,	3.62,	widow_step,
+	ai_run,	3.29,	NULL,
+	ai_run,	6.08,	NULL,			//10
+	ai_run,	6.94,	NULL,
+	ai_run,	5.73,	NULL,
+	ai_run,	2.85,	NULL
+};
+mmove_t widow_move_run = {FRAME_walk01, FRAME_walk13, widow_frames_run, NULL};
+
+void widow_stepshoot (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM,0);
+	WidowBlaster (self);
+}
+
+mframe_t widow_frames_run_attack [] =
+{
+	ai_charge,	13,	widow_stepshoot,
+	ai_charge,	11.72,	WidowBlaster,
+	ai_charge,	18.04,	WidowBlaster,
+	ai_charge,	14.58,	WidowBlaster,
+	ai_charge,	13,	widow_stepshoot,			//5
+	ai_charge,	12.12,	WidowBlaster,
+	ai_charge,	19.63,	WidowBlaster,
+	ai_charge,	11.37,	WidowBlaster
+};
+mmove_t widow_move_run_attack = {FRAME_run01, FRAME_run08, widow_frames_run_attack, widow_run};
+
+
+//
+// These three allow specific entry into the run sequence
+//
+
+void widow_start_run_5 (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow_move_run;
+	self->monsterinfo.nextframe = FRAME_walk05;
+}
+
+void widow_start_run_10 (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow_move_run;
+	self->monsterinfo.nextframe = FRAME_walk10;
+}
+
+void widow_start_run_12 (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow_move_run;
+	self->monsterinfo.nextframe = FRAME_walk12;
+}
+
+
+mframe_t widow_frames_attack_pre_blaster [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_attack_blaster
+};
+mmove_t widow_move_attack_pre_blaster = {FRAME_fired01, FRAME_fired02a, widow_frames_attack_pre_blaster, NULL};
+
+// Loop this
+mframe_t widow_frames_attack_blaster [] =
+{
+	ai_charge,	0,	widow_reattack_blaster,		// straight ahead
+	ai_charge,	0,	widow_reattack_blaster,		// 100 degrees right
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,		// 50 degrees right
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,		// straight
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster,		// 50 degrees left
+	ai_charge,	0,	widow_reattack_blaster,
+	ai_charge,	0,	widow_reattack_blaster		// 70 degrees left
+};
+mmove_t widow_move_attack_blaster = {FRAME_fired02a, FRAME_fired20, widow_frames_attack_blaster, NULL};
+
+mframe_t widow_frames_attack_post_blaster [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t widow_move_attack_post_blaster = {FRAME_fired21, FRAME_fired22, widow_frames_attack_post_blaster, widow_run};
+
+mframe_t widow_frames_attack_post_blaster_r [] =
+{
+	ai_charge,	-2,	NULL,
+	ai_charge,	-10,	NULL,
+	ai_charge,	-2,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_start_run_12
+};
+mmove_t widow_move_attack_post_blaster_r = {FRAME_transa01, FRAME_transa05, widow_frames_attack_post_blaster_r, NULL};
+
+mframe_t widow_frames_attack_post_blaster_l [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	14,	NULL,
+	ai_charge,	-2,	NULL,
+	ai_charge,	10,	NULL,
+	ai_charge,	10,	widow_start_run_12
+};
+mmove_t widow_move_attack_post_blaster_l = {FRAME_transb01, FRAME_transb05, widow_frames_attack_post_blaster_l, NULL};
+
+mmove_t widow_move_attack_rail;
+mmove_t widow_move_attack_rail_l;
+mmove_t widow_move_attack_rail_r;
+
+void WidowRail (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+	int		flash = 0;
+
+//	gi.dprintf ("railing!\n");
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+	if (self->monsterinfo.currentmove == &widow_move_attack_rail)
+		flash = MZ2_WIDOW_RAIL;
+	else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l)
+	{
+		flash = MZ2_WIDOW_RAIL_LEFT;
+	}
+	else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r)
+	{
+		flash = MZ2_WIDOW_RAIL_RIGHT;
+	}
+
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash], forward, right, start);
+
+	// calc direction to where we targeted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, WIDOW_RAIL_DAMAGE*widow_damage_multiplier, 100, flash);
+	self->timestamp = level.time + RAIL_TIME;
+}
+
+void WidowSaveLoc (edict_t *self)
+{
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+};
+
+void widow_start_rail (edict_t *self)
+{
+	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+}
+
+void widow_rail_done (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+}
+
+mframe_t widow_frames_attack_pre_rail [] =
+{
+	ai_charge,	0,	widow_start_rail,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_attack_rail
+};
+mmove_t widow_move_attack_pre_rail = {FRAME_transc01, FRAME_transc04, widow_frames_attack_pre_rail, NULL};
+
+mframe_t widow_frames_attack_rail [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, WidowSaveLoc,
+	ai_charge, -10, WidowRail,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, widow_rail_done
+};
+mmove_t widow_move_attack_rail = {FRAME_firea01, FRAME_firea09, widow_frames_attack_rail, widow_run};
+
+mframe_t widow_frames_attack_rail_r [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, WidowSaveLoc,
+	ai_charge, -10, WidowRail,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, widow_rail_done
+};
+mmove_t widow_move_attack_rail_r = {FRAME_fireb01, FRAME_fireb09, widow_frames_attack_rail_r, widow_run};
+
+mframe_t widow_frames_attack_rail_l [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, WidowSaveLoc,
+	ai_charge, -10, WidowRail,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, widow_rail_done
+};
+mmove_t widow_move_attack_rail_l = {FRAME_firec01, FRAME_firec09, widow_frames_attack_rail_l, widow_run};
+
+void widow_attack_rail (edict_t *self)
+{
+	float	enemy_angle;
+//	gi.dprintf ("going to the rail!\n");
+
+	enemy_angle = target_angle (self);
+
+	if (enemy_angle < -15)
+		self->monsterinfo.currentmove = &widow_move_attack_rail_l;
+	else if (enemy_angle > 15)
+		self->monsterinfo.currentmove = &widow_move_attack_rail_r;
+	else
+		self->monsterinfo.currentmove = &widow_move_attack_rail;
+}
+
+void widow_start_spawn (edict_t *self)
+{
+	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+}
+
+void widow_done_spawn (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+}
+
+mframe_t widow_frames_spawn [] =
+{
+	ai_charge,	0,	NULL,						//1
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_start_spawn,
+	ai_charge,	0,	NULL,						//5
+	ai_charge,	0,	WidowBlaster,				//6
+	ai_charge,	0,	widow_ready_spawn,			//7
+	ai_charge,	0,	WidowBlaster,
+	ai_charge,	0,	WidowBlaster,				//9
+	ai_charge,	0,	widow_spawn_check,
+	ai_charge,	0,	WidowBlaster,				//11
+	ai_charge,	0,	WidowBlaster,
+	ai_charge,	0,	WidowBlaster,				//13
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_done_spawn
+};
+mmove_t widow_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow_frames_spawn, widow_run};
+
+mframe_t widow_frames_pain_heavy [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t widow_move_pain_heavy = {FRAME_pain01, FRAME_pain13, widow_frames_pain_heavy, widow_run};
+
+mframe_t widow_frames_pain_light [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t widow_move_pain_light = {FRAME_pain201, FRAME_pain203, widow_frames_pain_light, widow_run};
+
+void spawn_out_start (edict_t *self)
+{
+	vec3_t startpoint,f,r,u;
+	self->wait = level.time + 2.0;
+
+//	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	AngleVectors (self->s.angles, f, r, u);
+
+	G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WIDOWBEAMOUT);
+	gi.WriteShort (20001);
+	gi.WritePosition (startpoint);
+	gi.multicast (startpoint, MULTICAST_ALL);
+
+	G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WIDOWBEAMOUT);
+	gi.WriteShort (20002);
+	gi.WritePosition (startpoint);
+	gi.multicast (startpoint, MULTICAST_ALL);
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0);
+}
+
+void spawn_out_do (edict_t *self)
+{
+	vec3_t startpoint,f,r,u;
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WIDOWSPLASH);
+	gi.WritePosition (startpoint);
+	gi.multicast (startpoint, MULTICAST_ALL);
+
+	G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WIDOWSPLASH);
+	gi.WritePosition (startpoint);
+	gi.multicast (startpoint, MULTICAST_ALL);
+
+	VectorCopy (self->s.origin, startpoint);
+	startpoint[2] += 36;
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (startpoint);
+	gi.multicast (startpoint, MULTICAST_PVS);
+
+	Widowlegs_Spawn (self->s.origin, self->s.angles);
+	
+	G_FreeEdict (self);
+}
+
+mframe_t widow_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		//5
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	spawn_out_start,	//10
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,				//15	
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,				//20	
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,				//25
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,		
+	ai_move,	0,	NULL,				//30	
+	ai_move,	0,	spawn_out_do
+};
+mmove_t widow_move_death = {FRAME_death01, FRAME_death31, widow_frames_death, NULL};
+
+void widow_attack_kick (edict_t *self)
+{
+	vec3_t	aim;
+
+//	VectorSet (aim, MELEE_DISTANCE, 0, 4);
+	VectorSet (aim, 100, 0, 4);
+	if (self->enemy->groundentity)
+		fire_hit (self, aim, (50 + (rand() % 6)), 500);
+	else	// not as much kick if they're in the air .. makes it harder to land on her head
+		fire_hit (self, aim, (50 + (rand() % 6)), 250);
+
+}
+
+mframe_t widow_frames_attack_kick [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, widow_attack_kick,
+	ai_move, 0, NULL,				// 5
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+
+mmove_t widow_move_attack_kick = {FRAME_kick01, FRAME_kick08, widow_frames_attack_kick, widow_run};
+
+void widow_stand (edict_t *self)
+{
+//	gi.dprintf ("widow stand\n");
+	gi.sound (self, CHAN_WEAPON, gi.soundindex ("widow/laugh.wav"), 1, ATTN_NORM, 0);
+	self->monsterinfo.currentmove = &widow_move_stand;
+}
+
+void widow_run (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &widow_move_stand;
+	else
+		self->monsterinfo.currentmove = &widow_move_run;
+}
+
+void widow_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow_move_walk;
+}
+
+void widow_attack (edict_t *self)
+{
+	float	luck;
+	qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false;
+
+	self->movetarget = NULL;
+
+	if (self->monsterinfo.aiflags & AI_BLOCKED)
+	{
+		blocked = true;
+		self->monsterinfo.aiflags &= ~AI_BLOCKED;
+	}
+	
+	if (self->monsterinfo.aiflags & AI_TARGET_ANGER)
+	{
+		anger = true;
+		self->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
+	}
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+		return;
+
+	if (self->bad_area)
+	{
+		if ((qrandom() < 0.1) || (level.time < self->timestamp))
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+		else
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+		}
+		return;
+	}
+
+	// frames FRAME_walk13, FRAME_walk01, FRAME_walk02, FRAME_walk03 are rail gun start frames
+	// frames FRAME_walk09, FRAME_walk10, FRAME_walk11, FRAME_walk12 are spawn & blaster start frames
+
+	if ((self->s.frame == FRAME_walk13) || ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03)))
+		rail_frames = true;
+
+	if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12))
+		blaster_frames = true;
+
+	WidowCalcSlots(self);
+
+	// if we can't see the target, spawn stuff regardless of frame
+	if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("attacking blind!\n");
+		self->monsterinfo.currentmove = &widow_move_spawn;
+		return;
+	}
+
+	// accept bias towards spawning regardless of frame
+	if (blocked && (SELF_SLOTS_LEFT >= 2))
+	{
+		self->monsterinfo.currentmove = &widow_move_spawn;
+		return;
+	}
+
+	if ((realrange(self, self->enemy) > 300) && (!anger) && (qrandom() < 0.5)  && (!blocked))
+	{
+		self->monsterinfo.currentmove = &widow_move_run_attack;
+		return;
+	}
+
+	if (blaster_frames)
+	{
+//		gi.dprintf ("blaster frame %2.2f <= %2.2f\n", self->monsterinfo.pausetime + BLASTER_TIME, level.time);
+		if (SELF_SLOTS_LEFT >= 2)
+		{
+			self->monsterinfo.currentmove = &widow_move_spawn;
+			return;
+		}
+		else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)
+		{
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+			return;
+		}
+	}
+
+	if (rail_frames)
+	{
+//		gi.dprintf ("rail frame %2.2f - %2.2f\n", level.time, self->timestamp);
+		if (!(level.time < self->timestamp))
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+		}
+	}
+
+	if ((rail_frames) || (blaster_frames))
+		return;
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//		gi.dprintf ("widow: unknown start frame, picking randomly\n");
+
+	luck = qrandom();
+	if (SELF_SLOTS_LEFT >= 2)
+	{
+		if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time))
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+		else if ((luck <= 0.7) && !(level.time < self->timestamp))
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+		}
+		else
+			self->monsterinfo.currentmove = &widow_move_spawn;
+	}
+	else
+	{
+		if (level.time < self->timestamp)
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+		else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime))
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+		}
+		else // holdout to blaster
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+	}
+}
+/*
+void widow_attack (edict_t *self)
+{
+	float	range, luck;
+
+//	gi.dprintf ("widow attack\n");
+	
+	if ((!self->enemy) || (!self->enemy->inuse))
+		return;
+
+	if (self->bad_area)
+	{
+		if ((random() < 0.1) || (level.time < self->timestamp))
+			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+		else
+		{
+			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+		}
+		return;
+	}
+
+	// if we can't see the target, spawn stuff
+	if ((self->monsterinfo.attack_state == AS_BLIND) && (blaster_frames))
+	{
+		self->monsterinfo.currentmove = &widow_move_spawn;
+		return;
+	}
+
+	range = realrange (self, self->enemy);
+
+	if (range < 600)
+	{
+		luck = qrandom();
+		if (SLOTS_LEFT >= 2)
+		{
+			if (luck <= 0.40)
+				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+			else if ((luck <= 0.7) && !(level.time < self->timestamp))
+			{
+				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+			}
+			else
+				self->monsterinfo.currentmove = &widow_move_spawn;
+		}
+		else
+		{
+			if ((luck <= 0.50) || (level.time < self->timestamp))
+				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+			else
+			{
+				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+			}
+		}
+	}
+	else
+	{
+		luck = qrandom();
+		if (SLOTS_LEFT >= 2)
+		{
+			if (luck < 0.3)
+				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+			else if ((luck < 0.65) || (level.time < self->timestamp))
+				self->monsterinfo.currentmove = &widow_move_spawn;
+			else
+			{
+				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+			}
+		}
+		else
+		{
+			if ((luck < 0.45) || (level.time < self->timestamp))
+				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
+			else
+			{
+				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
+			}
+		}
+	}
+}
+*/
+void widow_attack_blaster (edict_t *self)
+{
+	self->monsterinfo.pausetime = level.time + 1.0 + (2.0*qrandom());
+//	self->monsterinfo.pausetime = level.time + 100;
+//	self->plat2flags = 0;
+	self->monsterinfo.currentmove = &widow_move_attack_blaster;
+	self->monsterinfo.nextframe = WidowTorso (self);
+}
+
+void widow_reattack_blaster (edict_t *self)
+{
+	WidowBlaster(self);
+
+//	if ((g_showlogic) && (g_showlogic->value))
+//	{
+//		if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l)
+//			gi.dprintf ("pulling left!\n");
+//		if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r)
+//			gi.dprintf ("pulling right!\n");
+//	}
+
+//	self->monsterinfo.currentmove = &widow_move_attack_blaster;
+//		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
+//	return;
+	// if WidowBlaster bailed us out of the frames, just bail
+	if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) ||
+		(self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r))
+		return;
+
+	// if we're not done with the attack, don't leave the sequence
+	if (self->monsterinfo.pausetime >= level.time)
+		return;
+
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+	self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
+}
+/*
+	if ( infront(self, self->enemy) )
+		if (random() <= 0.5)
+			if ((random() < 0.7) || (SLOTS_LEFT <= 1))
+				self->monsterinfo.currentmove = &widow_move_attack_blaster;
+			else
+				self->monsterinfo.currentmove = &widow_move_spawn;
+		else
+			self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
+	else
+		self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
+}
+*/
+
+
+void widow_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	if (self->monsterinfo.pausetime == 100000000)
+		self->monsterinfo.pausetime = 0;
+
+	self->pain_debounce_time = level.time + 5;
+
+	if (damage < 15)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+	}
+	else if (damage < 75)
+	{
+		if ((skill->value < 3) && (qrandom() < (0.6 - (0.2*((float)skill->value)))))
+		{
+			self->monsterinfo.currentmove = &widow_move_pain_light;
+			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		}
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+	}
+	else 
+	{
+		if ((skill->value < 3) && (qrandom() < (0.75 - (0.1*((float)skill->value)))))
+		{
+			self->monsterinfo.currentmove = &widow_move_pain_heavy;
+			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+		}
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+	}
+}
+
+void widow_dead (edict_t *self)
+{
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void widow_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.quad_framenum = 0;
+	self->monsterinfo.double_framenum = 0;
+	self->monsterinfo.invincible_framenum = 0;
+	self->monsterinfo.currentmove = &widow_move_death;
+}
+
+void widow_melee (edict_t *self)
+{
+//	monster_done_dodge (self);
+	self->monsterinfo.currentmove = &widow_move_attack_kick;
+}
+
+void WidowGoinQuad (edict_t *self, float framenum)
+{
+	self->monsterinfo.quad_framenum = framenum;
+	widow_damage_multiplier = 4;
+}
+
+void WidowDouble (edict_t *self, float framenum)
+{
+	self->monsterinfo.double_framenum = framenum;
+	widow_damage_multiplier = 2;
+}
+
+void WidowPent (edict_t *self, float framenum)
+{
+	self->monsterinfo.invincible_framenum = framenum;
+}
+
+void WidowPowerArmor (edict_t *self)
+{
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+	// I don't like this, but it works
+	if (self->monsterinfo.power_armor_power <= 0)
+		self->monsterinfo.power_armor_power += 250 * skill->value;
+}
+
+void WidowRespondPowerup (edict_t *self, edict_t *other)
+{
+	if (other->s.effects & EF_QUAD)
+	{
+		if (skill->value == 1)
+			WidowDouble (self, other->client->quad_framenum);
+		else if (skill->value == 2)
+			WidowGoinQuad (self, other->client->quad_framenum);
+		else if (skill->value == 3)
+		{
+			WidowGoinQuad (self, other->client->quad_framenum);
+			WidowPowerArmor (self);
+		}
+	}
+	else if (other->s.effects & EF_DOUBLE)
+	{
+		if (skill->value == 2)
+			WidowDouble (self, other->client->double_framenum);
+		else if (skill->value == 3)
+		{
+			WidowDouble (self, other->client->double_framenum);
+			WidowPowerArmor (self);
+		}
+	}
+	else
+		widow_damage_multiplier = 1;
+
+	if (other->s.effects & EF_PENT)
+	{
+		if (skill->value == 1)
+			WidowPowerArmor (self);
+		else if (skill->value == 2)
+			WidowPent (self, other->client->invincible_framenum);
+		else if (skill->value == 3)
+		{
+			WidowPent (self, other->client->invincible_framenum);
+			WidowPowerArmor (self);
+		}
+	}
+}
+
+void WidowPowerups (edict_t *self)
+{
+	int player;
+	edict_t *ent;
+
+	if (!(coop && coop->value))
+	{
+		WidowRespondPowerup (self, self->enemy);
+	}
+	else
+	{
+		// in coop, check for pents, then quads, then doubles
+		for (player = 1; player <= game.maxclients; player++)
+		{
+			ent = &g_edicts[player];
+			if (!ent->inuse)
+				continue;
+			if (!ent->client)
+				continue;
+			if (ent->s.effects & EF_PENT)
+			{
+				WidowRespondPowerup (self, ent);
+				return;
+			}
+		}
+
+		for (player = 1; player <= game.maxclients; player++)
+		{
+			ent = &g_edicts[player];
+			if (!ent->inuse)
+				continue;
+			if (!ent->client)
+				continue;
+			if (ent->s.effects & EF_QUAD)
+			{
+				WidowRespondPowerup (self, ent);
+				return;
+			}
+		}
+
+		for (player = 1; player <= game.maxclients; player++)
+		{
+			ent = &g_edicts[player];
+			if (!ent->inuse)
+				continue;
+			if (!ent->client)
+				continue;
+			if (ent->s.effects & EF_DOUBLE)
+			{
+				WidowRespondPowerup (self, ent);
+				return;
+			}
+		}
+	}
+}
+
+qboolean Widow_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance = 0.0;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+	float		real_enemy_range;
+
+	if (!self->enemy)
+		return false;
+
+	WidowPowerups(self);
+
+	if (self->monsterinfo.currentmove == &widow_move_run)
+	{
+		// if we're in run, make sure we're in a good frame for attacking before doing anything else
+		// frames 1,2,3,9,10,11,13 good to fire
+		switch (self->s.frame)
+		{
+			case FRAME_walk04:
+			case FRAME_walk05:
+			case FRAME_walk06:
+			case FRAME_walk07:
+			case FRAME_walk08:
+			case FRAME_walk12:
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("Not in good walk frame (%d), not attacking\n", (self->s.frame - FRAME_walk01+1));
+					return false;
+				}
+			default:
+				break;
+		}
+	}
+
+	// give a LARGE bias to spawning things when we have room
+	// use AI_BLOCKED as a signal to attack to spawn
+	if ((qrandom() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150))
+	{
+		self->monsterinfo.aiflags |= AI_BLOCKED;
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// go ahead and spawn stuff if we're mad a a client
+			if (self->enemy->client && SELF_SLOTS_LEFT >= 2)
+			{
+				self->monsterinfo.attack_state = AS_BLIND;
+				return true;
+			}
+				
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+				return false;
+		}
+	}
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw2(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+	real_enemy_range = realrange (self, self->enemy);
+
+//	if (g_showlogic->value)
+//		gi.dprintf ("range = %2.2f\n", real_enemy_range);
+
+	// melee attack
+//	if (enemy_range == RANGE_MELEE)
+	if (real_enemy_range <= (MELEE_DISTANCE+20))
+	{
+		// don't always melee in easy mode
+		if (skill->value == 0 && (rand()&3) )
+			return false;
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.7;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.6;
+	}
+	else if (enemy_range == RANGE_FAR)
+	{
+		chance = 0.5;
+	}
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	if ((qrandom () < chance) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	return false;
+}
+
+qboolean widow_blocked (edict_t *self, float)
+{
+	// if we get blocked while we're in our run/attack mode, turn on a meaningless (in this context)AI flag, 
+	// and call attack to get a new attack sequence.  make sure to turn it off when we're done.
+	//
+	// I'm using AI_TARGET_ANGER for this purpose
+
+	if (self->monsterinfo.currentmove == &widow_move_run_attack)
+	{
+		self->monsterinfo.aiflags |= AI_TARGET_ANGER;
+		if (self->monsterinfo.checkattack(self))
+			self->monsterinfo.attack(self);
+		else
+			self->monsterinfo.run(self);
+		return true;
+	}
+
+	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
+		return true;
+
+/*
+	if(blocked_checkjump (self, dist, 192, 40))
+	{
+		infantry_jump(self);
+		return true;
+	}
+
+	if(blocked_checkplat (self, dist))
+		return true;
+*/
+	return false;
+}
+
+void WidowCalcSlots (edict_t *self)
+{
+	//int old_slots;
+
+	//old_slots = self->monsterinfo.monster_slots;
+
+	switch ((int)skill->value)
+	{
+		case 0:
+		case 1:
+			self->monsterinfo.monster_slots = 3;
+			break;
+		case 2:
+			self->monsterinfo.monster_slots = 4;
+			break;
+		case 3:
+			self->monsterinfo.monster_slots = 6;
+			break;
+		default:
+			self->monsterinfo.monster_slots = 3;
+			break;
+	}
+	if (coop->value)
+	{
+		self->monsterinfo.monster_slots = qmin(6, self->monsterinfo.monster_slots + ((skill->value)*(CountPlayers()-1)));
+	}
+//	if ((g_showlogic) && (g_showlogic->value) && (old_slots != self->monsterinfo.monster_slots))
+//		gi.dprintf ("number of slots changed from %d to %d\n", old_slots, self->monsterinfo.monster_slots);
+}
+
+void WidowPrecache (void)
+{
+	// cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs
+	gi.soundindex ("stalker/pain.wav");	
+	gi.soundindex ("stalker/death.wav");	
+	gi.soundindex ("stalker/sight.wav");
+	gi.soundindex ("stalker/melee1.wav");
+	gi.soundindex ("stalker/melee2.wav");
+	gi.soundindex ("stalker/idle.wav");
+
+	gi.soundindex ("tank/tnkatck3.wav");
+	gi.modelindex ("models/proj/laser2/tris.md2");
+
+	gi.modelindex ("models/monsters/stalker/tris.md2");
+	gi.modelindex ("models/items/spawngro2/tris.md2");
+	gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
+	gi.modelindex ("models/objects/gibs/gear/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2");
+	gi.modelindex ("models/monsters/legs/tris.md2");
+	gi.soundindex ("misc/bwidowbeamout.wav");
+
+	gi.soundindex ("misc/bigtele.wav");
+	gi.soundindex ("widow/bwstep3.wav");
+	gi.soundindex ("widow/bwstep2.wav");
+}
+
+
+/*QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_widow (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("widow/bw1pain1.wav");
+	sound_pain2 = gi.soundindex ("widow/bw1pain2.wav");
+	sound_pain3 = gi.soundindex ("widow/bw1pain3.wav");
+	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
+//	sound_sight	= gi.soundindex ("widow/sight.wav");
+	sound_rail = gi.soundindex ("gladiator/railgun.wav");
+
+//	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/blackwidow/tris.md2");
+	VectorSet (self->mins, -40, -40, 0);
+	VectorSet (self->maxs, 40, 40, 144);
+
+	self->health = 2000 + 1000*(skill->value);
+	if (coop->value)
+		self->health += 500*(skill->value);
+//	self->health = 1;
+	self->gib_health = -5000;
+	self->mass = 1500;
+/*
+	if (skill->value == 2)
+	{
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+		self->monsterinfo.power_armor_power = 250;
+	}
+	else */if (skill->value == 3)
+	{
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+		self->monsterinfo.power_armor_power = 500;
+	}
+
+	self->yaw_speed = 30;
+	
+	self->flags |= FL_IMMUNE_LASER;
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+	self->pain = widow_pain;
+	self->die = widow_die;
+
+	self->monsterinfo.melee = widow_melee;
+	self->monsterinfo.stand = widow_stand;
+	self->monsterinfo.walk = widow_walk;
+	self->monsterinfo.run = widow_run;
+	self->monsterinfo.attack = widow_attack;
+	self->monsterinfo.search = widow_search;
+	self->monsterinfo.checkattack = Widow_CheckAttack;
+	self->monsterinfo.sight = widow_sight;
+	
+	self->monsterinfo.blocked = widow_blocked;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &widow_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	WidowPrecache();
+	WidowCalcSlots(self);
+	widow_damage_multiplier = 1;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/rogue/m_widow.h
@@ -1,0 +1,175 @@
+// G:\quake2\xpack\models/monsters/blackwidow
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_idle01          	0
+#define FRAME_idle02          	1
+#define FRAME_idle03          	2
+#define FRAME_idle04          	3
+#define FRAME_idle05          	4
+#define FRAME_idle06          	5
+#define FRAME_idle07          	6
+#define FRAME_idle08          	7
+#define FRAME_idle09          	8
+#define FRAME_idle10          	9
+#define FRAME_idle11          	10
+#define FRAME_walk01          	11
+#define FRAME_walk02          	12
+#define FRAME_walk03          	13
+#define FRAME_walk04          	14
+#define FRAME_walk05          	15
+#define FRAME_walk06          	16
+#define FRAME_walk07          	17
+#define FRAME_walk08          	18
+#define FRAME_walk09          	19
+#define FRAME_walk10          	20
+#define FRAME_walk11          	21
+#define FRAME_walk12          	22
+#define FRAME_walk13          	23
+#define FRAME_run01           	24
+#define FRAME_run02           	25
+#define FRAME_run03           	26
+#define FRAME_run04           	27
+#define FRAME_run05           	28
+#define FRAME_run06           	29
+#define FRAME_run07           	30
+#define FRAME_run08           	31
+#define FRAME_firea01         	32
+#define FRAME_firea02         	33
+#define FRAME_firea03         	34
+#define FRAME_firea04         	35
+#define FRAME_firea05         	36
+#define FRAME_firea06         	37
+#define FRAME_firea07         	38
+#define FRAME_firea08         	39
+#define FRAME_firea09         	40
+#define FRAME_fireb01         	41
+#define FRAME_fireb02         	42
+#define FRAME_fireb03         	43
+#define FRAME_fireb04         	44
+#define FRAME_fireb05         	45
+#define FRAME_fireb06         	46
+#define FRAME_fireb07         	47
+#define FRAME_fireb08         	48
+#define FRAME_fireb09         	49
+#define FRAME_firec01         	50
+#define FRAME_firec02         	51
+#define FRAME_firec03         	52
+#define FRAME_firec04         	53
+#define FRAME_firec05         	54
+#define FRAME_firec06         	55
+#define FRAME_firec07         	56
+#define FRAME_firec08         	57
+#define FRAME_firec09         	58
+#define FRAME_fired01         	59
+#define FRAME_fired02         	60
+#define FRAME_fired02a        	61
+#define FRAME_fired03         	62
+#define FRAME_fired04         	63
+#define FRAME_fired05         	64
+#define FRAME_fired06         	65
+#define FRAME_fired07         	66
+#define FRAME_fired08         	67
+#define FRAME_fired09         	68
+#define FRAME_fired10         	69
+#define FRAME_fired11         	70
+#define FRAME_fired12         	71
+#define FRAME_fired13         	72
+#define FRAME_fired14         	73
+#define FRAME_fired15         	74
+#define FRAME_fired16         	75
+#define FRAME_fired17         	76
+#define FRAME_fired18         	77
+#define FRAME_fired19         	78
+#define FRAME_fired20         	79
+#define FRAME_fired21         	80
+#define FRAME_fired22         	81
+#define FRAME_spawn01         	82
+#define FRAME_spawn02         	83
+#define FRAME_spawn03         	84
+#define FRAME_spawn04         	85
+#define FRAME_spawn05         	86
+#define FRAME_spawn06         	87
+#define FRAME_spawn07         	88
+#define FRAME_spawn08         	89
+#define FRAME_spawn09         	90
+#define FRAME_spawn10         	91
+#define FRAME_spawn11         	92
+#define FRAME_spawn12         	93
+#define FRAME_spawn13         	94
+#define FRAME_spawn14         	95
+#define FRAME_spawn15         	96
+#define FRAME_spawn16         	97
+#define FRAME_spawn17         	98
+#define FRAME_spawn18         	99
+#define FRAME_pain01          	100
+#define FRAME_pain02          	101
+#define FRAME_pain03          	102
+#define FRAME_pain04          	103
+#define FRAME_pain05          	104
+#define FRAME_pain06          	105
+#define FRAME_pain07          	106
+#define FRAME_pain08          	107
+#define FRAME_pain09          	108
+#define FRAME_pain10          	109
+#define FRAME_pain11          	110
+#define FRAME_pain12          	111
+#define FRAME_pain13          	112
+#define FRAME_pain201         	113
+#define FRAME_pain202         	114
+#define FRAME_pain203         	115
+#define FRAME_transa01        	116
+#define FRAME_transa02        	117
+#define FRAME_transa03        	118
+#define FRAME_transa04        	119
+#define FRAME_transa05        	120
+#define FRAME_transb01        	121
+#define FRAME_transb02        	122
+#define FRAME_transb03        	123
+#define FRAME_transb04        	124
+#define FRAME_transb05        	125
+#define FRAME_transc01        	126
+#define FRAME_transc02        	127
+#define FRAME_transc03        	128
+#define FRAME_transc04        	129
+#define FRAME_death01         	130
+#define FRAME_death02         	131
+#define FRAME_death03         	132
+#define FRAME_death04         	133
+#define FRAME_death05         	134
+#define FRAME_death06         	135
+#define FRAME_death07         	136
+#define FRAME_death08         	137
+#define FRAME_death09         	138
+#define FRAME_death10         	139
+#define FRAME_death11         	140
+#define FRAME_death12         	141
+#define FRAME_death13         	142
+#define FRAME_death14         	143
+#define FRAME_death15         	144
+#define FRAME_death16         	145
+#define FRAME_death17         	146
+#define FRAME_death18         	147
+#define FRAME_death19         	148
+#define FRAME_death20         	149
+#define FRAME_death21         	150
+#define FRAME_death22         	151
+#define FRAME_death23         	152
+#define FRAME_death24         	153
+#define FRAME_death25         	154
+#define FRAME_death26         	155
+#define FRAME_death27         	156
+#define FRAME_death28         	157
+#define FRAME_death29         	158
+#define FRAME_death30         	159
+#define FRAME_death31         	160
+#define FRAME_kick01          	161
+#define FRAME_kick02          	162
+#define FRAME_kick03          	163
+#define FRAME_kick04          	164
+#define FRAME_kick05          	165
+#define FRAME_kick06          	166
+#define FRAME_kick07          	167
+#define FRAME_kick08          	168
+
+#define MODEL_SCALE		2.000000
--- /dev/null
+++ b/rogue/m_widow2.c
@@ -1,0 +1,1784 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_widow2.h"
+
+// timestamp used to prevent rapid fire of melee attack
+
+
+#define	NUM_STALKERS_SPAWNED		6		// max # of stalkers she can spawn
+
+#define	DISRUPT_TIME					3
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_disrupt;
+static int	sound_tentacles_retract;
+
+// sqrt(64*64*2) + sqrt(28*28*2) => 130.1
+static vec3_t spawnpoints[] = {
+	{30,  135, 0},
+	{30, -135, 0}
+};
+
+static float sweep_angles[] = {
+	-40.0, -32.0, -24.0, -16.0, -8.0, 0.0, 8.0, 16.0, 24.0, 32.0, 40.0
+};
+
+extern vec3_t	stalker_mins, stalker_maxs;
+
+qboolean infront (edict_t *self, edict_t *other);
+void WidowCalcSlots (edict_t *self);
+void WidowPowerups (edict_t *self);
+
+void widow2_run (edict_t *self);
+void widow2_stand (edict_t *self);
+void widow2_dead (edict_t *self);
+void widow2_attack (edict_t *self);
+void widow2_attack_beam (edict_t *self);
+void widow2_reattack_beam (edict_t *self);
+void widow2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void widow_start_spawn (edict_t *self);
+void widow_done_spawn (edict_t *self);
+void widow2_spawn_check (edict_t *self);
+void widow2_prep_spawn (edict_t *self);
+void Widow2SaveBeamTarget(edict_t *self);
+
+// death stuff
+void WidowExplode (edict_t *self);
+void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+void ThrowWidowGibReal (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean large, int hitsound, qboolean fade);
+void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade);
+void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade);
+void WidowExplosion1 (edict_t *self);
+void WidowExplosion2 (edict_t *self);
+void WidowExplosion3 (edict_t *self);
+void WidowExplosion4 (edict_t *self);
+void WidowExplosion5 (edict_t *self);
+void WidowExplosion6 (edict_t *self);
+void WidowExplosion7 (edict_t *self);
+void WidowExplosionLeg (edict_t *self);
+void ThrowArm1 (edict_t *self);
+void ThrowArm2 (edict_t *self);
+void ClipGibVelocity (edict_t *ent);
+// end of death stuff
+
+// these offsets used by the tongue
+static vec3_t offsets[] = {
+	{17.48, 0.10, 68.92},
+	{17.47, 0.29, 68.91},
+	{17.45, 0.53, 68.87},
+	{17.42, 0.78, 68.81},
+	{17.39, 1.02, 68.75},
+	{17.37, 1.20, 68.70},
+	{17.36, 1.24, 68.71},
+	{17.37, 1.21, 68.72},
+};
+
+void showme (edict_t *self);
+
+void pauseme (edict_t *self)
+{
+	self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void widow2_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+}
+
+void Widow2Beam (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start, targ_angles, vec;
+	int		flashnum;
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+		return;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	
+	if ((self->s.frame >= FRAME_fireb05) && (self->s.frame <= FRAME_fireb09))
+	{
+		// regular beam attack
+		Widow2SaveBeamTarget(self);
+		flashnum = MZ2_WIDOW2_BEAMER_1 + self->s.frame - FRAME_fireb05;
+		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+		VectorCopy (self->pos2, target);
+		target[2] += self->enemy->viewheight-10;
+		VectorSubtract (target, start, forward);
+		VectorNormalize (forward);
+		monster_fire_heat (self, start, forward, vec3_origin, 10, 50, flashnum);
+	}
+	else if ((self->s.frame >= FRAME_spawn04) && (self->s.frame <= FRAME_spawn14))
+	{
+		// sweep
+		flashnum = MZ2_WIDOW2_BEAM_SWEEP_1 + self->s.frame - FRAME_spawn04;
+		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
+		VectorSubtract (self->enemy->s.origin, start, target);
+		vectoangles2 (target, targ_angles);
+		
+		VectorCopy (self->s.angles, vec);
+
+		vec[PITCH] += targ_angles[PITCH];
+		vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW2_BEAM_SWEEP_1];
+
+		AngleVectors (vec, forward, NULL, NULL);
+		monster_fire_heat (self, start, forward, vec3_origin, 10, 50, flashnum);
+/*
+		if (self->s.frame == FRAME_spawn04)
+		{
+			VectorMA (start, 1024, forward, debugend);
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_DEBUGTRAIL);
+			gi.WritePosition (start);
+			gi.WritePosition (debugend);
+			gi.multicast (start, MULTICAST_ALL);
+
+			drawbbox (self);
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING;
+		}
+*/
+	}
+	else
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("bad fire frame for widow2 beam -- tell me you saw this!\n");
+
+		Widow2SaveBeamTarget(self);
+		G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_WIDOW2_BEAMER_1], forward, right, start);
+
+		VectorCopy (self->pos2, target);
+		target[2] += self->enemy->viewheight-10;
+		
+		VectorSubtract (target, start, forward);
+		VectorNormalize (forward);
+
+		monster_fire_heat (self, start, forward, vec3_origin, 10, 50, 0);
+	}	
+}
+
+void Widow2Spawn (edict_t *self)
+{
+	vec3_t	f, r, u, offset, startpoint, spawnpoint;
+	edict_t	*ent, *designated_enemy;
+	int		i;
+
+	AngleVectors (self->s.angles, f, r, u);
+
+	for (i=0; i < 2; i++)
+	{
+		VectorCopy (spawnpoints[i], offset);
+
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+		{
+			ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256);
+
+			if (!ent)
+				continue;
+
+			self->monsterinfo.monster_used++;
+			ent->monsterinfo.commander = self;
+//			if ((g_showlogic) && (g_showlogic->value))
+//				gi.dprintf ("widow: post-spawn : %d slots left\n", SELF_SLOTS_LEFT);
+
+			ent->nextthink = level.time;
+			ent->think (ent);
+			
+			ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
+
+			if (!(coop && coop->value))
+			{
+				designated_enemy = self->enemy;
+			}
+			else
+			{
+				designated_enemy = PickCoopTarget(ent);
+				if (designated_enemy)
+				{
+					// try to avoid using my enemy
+					if (designated_enemy == self->enemy)
+					{
+						designated_enemy = PickCoopTarget(ent);
+						if (designated_enemy)
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//							{
+//								gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
+//								if (designated_enemy->client)
+//									gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
+//								else
+//									gi.dprintf ("NOT A CLIENT\n");
+//							}
+						}
+						else
+						{
+//							if ((g_showlogic) && (g_showlogic->value))
+//								gi.dprintf ("pick coop failed, using my current enemy\n");
+							designated_enemy = self->enemy;
+						}
+					}
+				}
+				else
+				{
+//					if ((g_showlogic) && (g_showlogic->value))
+//						gi.dprintf ("pick coop failed, using my current enemy\n");
+					designated_enemy = self->enemy;
+				}
+			}
+
+			if ((designated_enemy->inuse) && (designated_enemy->health > 0))
+			{
+				ent->enemy = designated_enemy;
+				FoundTarget (ent);
+				ent->monsterinfo.attack(ent);
+			}
+		}
+	}
+}
+
+void widow2_spawn_check (edict_t *self)
+{
+	Widow2Beam(self);
+	Widow2Spawn (self);
+}
+
+void widow2_ready_spawn (edict_t *self)
+{
+	vec3_t	f, r, u, offset, startpoint, spawnpoint;
+	int		i;
+
+	Widow2Beam(self);
+	AngleVectors (self->s.angles, f, r, u);
+
+	for (i=0; i < 2; i++)
+	{
+		VectorCopy (spawnpoints[i], offset);
+		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
+		{
+			SpawnGrow_Spawn (spawnpoint, 1);
+		}
+	}
+}
+
+mframe_t widow2_frames_stand [] =
+{
+//	ai_stand, 0, drawbbox
+	ai_stand, 0, NULL
+};
+mmove_t	widow2_move_stand = {FRAME_blackwidow3, FRAME_blackwidow3, widow2_frames_stand, NULL};
+
+mframe_t widow2_frames_walk [] =
+{
+//	ai_walk,	9.01,	drawbbox,
+	ai_walk,	9.01,	NULL,
+	ai_walk,	7.55,	NULL,
+	ai_walk,	7.01,	NULL,
+	ai_walk,	6.66,	NULL,
+	ai_walk,	6.20,	NULL,
+	ai_walk,	5.78,	NULL,
+	ai_walk,	7.25,	NULL,
+	ai_walk,	8.37,	NULL,
+	ai_walk,	10.41,	NULL
+};
+mmove_t widow2_move_walk = {FRAME_walk01, FRAME_walk09, widow2_frames_walk, NULL};
+
+
+mframe_t widow2_frames_run [] =
+{
+//	ai_run,	9.01,	drawbbox,
+	ai_run,	9.01,	NULL,
+	ai_run,	7.55,	NULL,
+	ai_run,	7.01,	NULL,
+	ai_run,	6.66,	NULL,
+	ai_run,	6.20,	NULL,
+	ai_run,	5.78,	NULL,
+	ai_run,	7.25,	NULL,
+	ai_run,	8.37,	NULL,
+	ai_run,	10.41,	NULL
+};
+mmove_t widow2_move_run = {FRAME_walk01, FRAME_walk09, widow2_frames_run, NULL};
+
+mframe_t widow2_frames_attack_pre_beam [] =
+{
+	ai_charge,	4,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	4,	widow2_attack_beam
+};
+mmove_t widow2_move_attack_pre_beam = {FRAME_fireb01, FRAME_fireb04, widow2_frames_attack_pre_beam, NULL};
+
+
+// Loop this
+mframe_t widow2_frames_attack_beam [] =
+{
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	widow2_reattack_beam
+};
+mmove_t widow2_move_attack_beam = {FRAME_fireb05, FRAME_fireb09, widow2_frames_attack_beam, NULL};
+
+mframe_t widow2_frames_attack_post_beam [] =
+{
+	ai_charge,	4,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	4,	NULL
+};
+mmove_t widow2_move_attack_post_beam = {FRAME_fireb06, FRAME_fireb07, widow2_frames_attack_post_beam, widow2_run};
+
+
+void WidowDisrupt (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+	float	len;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_WIDOW_DISRUPTOR], forward, right, start);
+
+	VectorSubtract (self->pos1, self->enemy->s.origin, dir);
+	len = VectorLength (dir);
+
+	if (len < 30)
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("target locked - dist %2.2f\n", len);
+		// calc direction to where we targeted
+		VectorSubtract (self->pos1, start, dir);
+		VectorNormalize (dir);
+
+		monster_fire_tracker(self, start, dir, 20, 500, self->enemy, MZ2_WIDOW_DISRUPTOR);
+	}
+	else
+	{
+//		if ((g_showlogic) && (g_showlogic->value))
+//			gi.dprintf ("target missed - dist %2.2f\n", len);
+
+		PredictAim (self->enemy, start, 1200, true, 0, dir, NULL);
+
+//		VectorSubtract (self->enemy->s.origin, start, dir);
+//		VectorNormalize (dir);
+		monster_fire_tracker(self, start, dir, 20, 1200, NULL, MZ2_WIDOW_DISRUPTOR);
+	}
+}
+
+void Widow2SaveDisruptLoc (edict_t *self)
+{
+	if (self->enemy && self->enemy->inuse)
+	{
+		VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+		self->pos1[2] += self->enemy->viewheight;
+	}
+	else
+		VectorCopy (vec3_origin, self->pos1);
+};
+
+void widow2_disrupt_reattack (edict_t *self)
+{
+	float luck;
+	
+	luck = qrandom();
+
+	if (luck < (0.25 + ((float)(skill->value))*0.15))
+		self->monsterinfo.nextframe = FRAME_firea01;
+}
+
+mframe_t widow2_frames_attack_disrupt [] =
+{
+	ai_charge, 2, NULL,
+	ai_charge, 2, NULL,
+	ai_charge, 2, Widow2SaveDisruptLoc,
+	ai_charge, -20, WidowDisrupt,
+	ai_charge, 2, NULL,
+	ai_charge, 2, NULL,
+	ai_charge, 2, widow2_disrupt_reattack
+};
+mmove_t widow2_move_attack_disrupt = {FRAME_firea01, FRAME_firea07, widow2_frames_attack_disrupt, widow2_run};
+
+void Widow2SaveBeamTarget (edict_t *self)
+{
+	if (self->enemy && self->enemy->inuse)
+	{
+		VectorCopy (self->pos1, self->pos2);
+		VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	}
+	else
+	{
+		VectorCopy (vec3_origin, self->pos1);
+		VectorCopy (vec3_origin, self->pos2);
+	}
+}
+
+void Widow2BeamTargetRemove (edict_t *self)
+{
+	VectorCopy (vec3_origin, self->pos1);
+	VectorCopy (vec3_origin, self->pos2);
+}
+
+void Widow2StartSweep (edict_t *self)
+{
+	Widow2SaveBeamTarget (self);
+}
+
+mframe_t widow2_frames_spawn [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow_start_spawn,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,				//5
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	widow2_ready_spawn,				//10
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	Widow2Beam,
+	ai_charge,	0,	widow2_spawn_check,
+	ai_charge,	0,	NULL,				//15
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	widow2_reattack_beam
+};
+mmove_t widow2_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow2_frames_spawn, NULL};
+
+static qboolean widow2_tongue_attack_ok (vec3_t start, vec3_t end, float range)
+{
+	vec3_t	dir, angles;
+
+	// check for max distance
+	VectorSubtract (start, end, dir);
+	if (VectorLength(dir) > range)
+		return false;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 30)
+		return false;
+
+	return true;
+}
+
+void Widow2Tongue (edict_t *self)
+{
+	vec3_t	f, r, u;
+	vec3_t	start, end, dir;
+	trace_t	tr;
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start);
+	VectorCopy (self->enemy->s.origin, end);
+	if (!widow2_tongue_attack_ok(start, end, 256))
+	{
+		end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+		if (!widow2_tongue_attack_ok(start, end, 256))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+			if (!widow2_tongue_attack_ok(start, end, 256))
+				return;
+		}
+	}
+	VectorCopy (self->enemy->s.origin, end);
+
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if (tr.ent != self->enemy)
+		return;
+
+	gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PARASITE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	VectorSubtract (start, end, dir);
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 2, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+void Widow2TonguePull (edict_t *self)
+{
+	vec3_t	vec;
+	vec3_t	f, r, u;
+	vec3_t	start, end;
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		self->monsterinfo.run (self);
+		return;
+	}
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offsets[self->s.frame - FRAME_tongs01], f, r, u, start);
+	VectorCopy (self->enemy->s.origin, end);
+
+	if (!widow2_tongue_attack_ok(start, end, 256))
+	{
+		return;
+	}
+
+	if (self->enemy->groundentity)
+	{
+		self->enemy->s.origin[2] += 1;
+		self->enemy->groundentity = NULL;
+		// interesting, you don't have to relink the player
+	}
+	
+	VectorSubtract (self->s.origin, self->enemy->s.origin, vec);
+	if (self->enemy->client)
+	{
+		VectorNormalize (vec);
+		VectorMA (self->enemy->velocity, 1000, vec, self->enemy->velocity);
+	}
+	else
+	{
+		self->enemy->ideal_yaw = vectoyaw(vec);	
+		M_ChangeYaw (self->enemy);
+		VectorScale (f, 1000, self->enemy->velocity);
+	}
+}
+
+void Widow2Crunch (edict_t *self)
+{
+	vec3_t	aim;
+
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		self->monsterinfo.run (self);
+		return;
+	}
+
+	Widow2TonguePull (self);
+
+	// 70 + 32
+	VectorSet (aim, 150, 0, 4);
+	if (self->s.frame != FRAME_tongs07)
+		fire_hit (self, aim, 20 + (rand() % 6), 0);
+	else
+	{
+		if (self->enemy->groundentity)
+			fire_hit (self, aim, (20 + (rand() % 6)), 500);
+		else	// not as much kick if they're in the air .. makes it harder to land on her head
+			fire_hit (self, aim, (20 + (rand() % 6)), 250);
+	}
+}
+
+void Widow2Toss (edict_t *self)
+{
+	self->timestamp = level.time + 3;
+	return;
+}
+
+mframe_t widow2_frames_tongs [] =
+{
+	ai_charge,	0,	Widow2Tongue,
+	ai_charge,	0,	Widow2Tongue,
+	ai_charge,	0,	Widow2Tongue,
+	ai_charge,	0,	Widow2TonguePull,
+	ai_charge,	0,	Widow2TonguePull,				//5
+	ai_charge,	0,	Widow2TonguePull,
+	ai_charge,	0,	Widow2Crunch,
+	ai_charge,	0,	Widow2Toss
+};
+mmove_t widow2_move_tongs = {FRAME_tongs01, FRAME_tongs08, widow2_frames_tongs, widow2_run};
+
+mframe_t widow2_frames_pain [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t widow2_move_pain = {FRAME_pain01, FRAME_pain05, widow2_frames_pain, widow2_run};
+
+mframe_t widow2_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion1,	// 3 boom
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,				// 5
+
+	ai_move,	0,	WidowExplosion2,	// 6 boom
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,				// 10
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,				// 12
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,				// 15
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion3,	// 18
+	ai_move,	0,	NULL,				// 19
+	ai_move,	0,	NULL,				// 20
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion4,	// 25
+
+	ai_move,	0,	NULL,				// 26
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion5,
+	ai_move,	0,	WidowExplosionLeg,	// 30
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion6,
+	ai_move,	0,	NULL,				// 35
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplosion7,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,				// 40
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	WidowExplode		// 44
+};
+mmove_t widow2_move_death = {FRAME_death01, FRAME_death44, widow2_frames_death, NULL};
+
+void widow2_start_searching (edict_t *self);
+void widow2_keep_searching (edict_t *self);
+void widow2_finaldeath (edict_t *self);
+
+mframe_t widow2_frames_dead [] =
+{
+	ai_move,	0,	widow2_start_searching,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	widow2_keep_searching
+};
+mmove_t widow2_move_dead = {FRAME_dthsrh01, FRAME_dthsrh15, widow2_frames_dead, NULL};
+
+mframe_t widow2_frames_really_dead [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	widow2_finaldeath
+};
+mmove_t widow2_move_really_dead = {FRAME_dthsrh16, FRAME_dthsrh22, widow2_frames_really_dead, NULL};
+
+void widow2_start_searching (edict_t *self)
+{
+	self->count = 0;
+}
+
+void widow2_keep_searching (edict_t *self)
+{
+	if (self->count <= 2)
+	{
+		self->monsterinfo.currentmove = &widow2_move_dead;
+		self->s.frame = FRAME_dthsrh01;
+		self->count++;
+		return;
+	}
+
+	self->monsterinfo.currentmove = &widow2_move_really_dead;
+}
+
+void widow2_finaldeath (edict_t *self)
+{
+	VectorSet (self->mins, -70, -70, 0);
+	VectorSet (self->maxs, 70, 70, 80);
+	self->movetype = MOVETYPE_TOSS;
+//	self->svflags |= SVF_DEADMONSTER;
+	self->takedamage = DAMAGE_YES;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void widow2_stand (edict_t *self)
+{
+//	gi.dprintf ("widow2 stand\n");
+	self->monsterinfo.currentmove = &widow2_move_stand;
+}
+
+void widow2_run (edict_t *self)
+{
+
+//	gi.dprintf ("widow2 run - %2.2f - %s \n", level.time, self->enemy->classname);
+	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &widow2_move_stand;
+	else
+		self->monsterinfo.currentmove = &widow2_move_run;
+}
+
+void widow2_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow2_move_walk;
+}
+
+void widow2_melee (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow2_move_tongs;
+}
+
+void widow2_attack (edict_t *self)
+{
+	float	range, luck;
+	qboolean blocked = false;
+
+	if (self->monsterinfo.aiflags & AI_BLOCKED)
+	{
+		blocked = true;
+		self->monsterinfo.aiflags &= ~AI_BLOCKED;
+	}
+
+//	gi.dprintf ("widow2 attack\n");
+	
+	if (!self->enemy)
+		return;
+
+	if (self->bad_area)
+	{
+		if ((qrandom() < 0.75) || (level.time < self->monsterinfo.attack_finished))
+			self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+		else
+		{
+			self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+		}
+		return;
+	}
+
+	WidowCalcSlots(self);
+
+	// if we can't see the target, spawn stuff
+	if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
+	{
+		self->monsterinfo.currentmove = &widow2_move_spawn;
+		return;
+	}
+
+	// accept bias towards spawning
+	if (blocked  && (SELF_SLOTS_LEFT >= 2))
+	{
+		self->monsterinfo.currentmove = &widow2_move_spawn;
+		return;
+	}
+
+	range = realrange (self, self->enemy);
+
+	if (range < 600)
+	{
+		luck = qrandom();
+		if (SELF_SLOTS_LEFT >= 2)
+		{
+			if (luck <= 0.40)
+				self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+			else if ((luck <= 0.7) && !(level.time < self->monsterinfo.attack_finished))
+			{
+//				gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+			}
+			else
+				self->monsterinfo.currentmove = &widow2_move_spawn;
+		}
+		else
+		{
+			if ((luck <= 0.50) || (level.time < self->monsterinfo.attack_finished))
+				self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+			else
+			{
+//				gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+			}
+		}
+	}
+	else
+	{
+		luck = qrandom();
+		if (SELF_SLOTS_LEFT >= 2)
+		{
+			if (luck < 0.3)
+				self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+			else if ((luck < 0.65) || (level.time < self->monsterinfo.attack_finished))
+				self->monsterinfo.currentmove = &widow2_move_spawn;
+			else
+			{
+//				gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+			}
+		}
+		else
+		{
+			if ((luck < 0.45) || (level.time < self->monsterinfo.attack_finished))
+				self->monsterinfo.currentmove = &widow2_move_attack_pre_beam;
+			else
+			{
+//				gi.sound (self, CHAN_WEAPON, sound_disrupt, 1, ATTN_NORM, 0);
+				self->monsterinfo.currentmove = &widow2_move_attack_disrupt;
+			}
+		}
+	}
+}
+
+void widow2_attack_beam (edict_t *self)
+{
+	self->monsterinfo.currentmove = &widow2_move_attack_beam;
+}
+
+void widow2_reattack_beam (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+
+	if ( infront(self, self->enemy) )
+		if (qrandom() <= 0.5)
+			if ((qrandom() < 0.7) || (SELF_SLOTS_LEFT < 2))
+				self->monsterinfo.currentmove = &widow2_move_attack_beam;
+			else
+				self->monsterinfo.currentmove = &widow2_move_spawn;
+		else
+			self->monsterinfo.currentmove = &widow2_move_attack_post_beam;
+	else
+		self->monsterinfo.currentmove = &widow2_move_attack_post_beam;
+}
+
+
+
+void widow2_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+//	gi.dprintf ("widow2 pain\n");
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 5;
+
+	if (damage < 15)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+	}
+	else if (damage < 75)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+		if ((skill->value < 3) && (qrandom() < (0.6 - (0.2*((float)skill->value)))))
+		{
+			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+			self->monsterinfo.currentmove = &widow2_move_pain;
+		}
+	}
+	else 
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+		if ((skill->value < 3) && (qrandom() < (0.75 - (0.1*((float)skill->value)))))
+		{
+			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
+			self->monsterinfo.currentmove = &widow2_move_pain;
+		}
+	}
+}
+
+void widow2_dead (edict_t *)
+{
+}
+
+void KillChildren (edict_t *self)
+{
+	edict_t *ent;
+	int		field;
+
+	ent = NULL;
+	field = FOFS(classname);
+	while (1)
+	{
+		ent = G_Find (ent, field, "monster_stalker");
+		if(!ent)
+			return;
+		
+		// FIXME - may need to stagger
+		if ((ent->inuse) && (ent->health > 0))
+			T_Damage (ent, self, self, vec3_origin, self->enemy->s.origin, vec3_origin, (ent->health + 1), 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+	}
+}
+
+void widow2_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int n;
+	int	clipped;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		clipped = qmin(damage, 100);
+
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowWidowGibLoc (self, "models/objects/gibs/bone/tris.md2", clipped, GIB_ORGANIC, NULL, false);
+		for (n= 0; n < 3; n++)
+			ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", clipped, GIB_ORGANIC, NULL, false);
+		for (n= 0; n < 3; n++)
+		{
+			ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib1/tris.md2", clipped, GIB_METALLIC, NULL,
+				0, false);
+			ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib2/tris.md2", clipped, GIB_METALLIC, NULL, 
+				gi.soundindex ("misc/fhit3.wav"), false);
+		}
+		for (n= 0; n < 2; n++)
+		{
+			ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib3/tris.md2", clipped, GIB_METALLIC, NULL, 
+				0, false);
+			ThrowWidowGibSized (self, "models/monsters/blackwidow/gib3/tris.md2", clipped, GIB_METALLIC, NULL, 
+				0, false);
+		}
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", clipped, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", clipped, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	KillChildren (self);
+	self->monsterinfo.quad_framenum = 0;
+	self->monsterinfo.double_framenum = 0;
+	self->monsterinfo.invincible_framenum = 0;
+	self->monsterinfo.currentmove = &widow2_move_death;
+}
+
+qboolean Widow2_CheckAttack (edict_t *self)
+{
+	vec3_t		spot1, spot2;
+	vec3_t		temp;
+	float		chance = 0.0;
+	trace_t		tr;
+	int			enemy_range;
+	float		enemy_yaw;
+	float		real_enemy_range;
+	vec3_t		f, r, u;
+
+	if (!self->enemy)
+		return false;
+
+	WidowPowerups(self);
+
+	if ((qrandom() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150))
+	{
+		self->monsterinfo.aiflags |= AI_BLOCKED;
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+		{	
+			// go ahead and spawn stuff if we're mad a a client
+			if (self->enemy->client && SELF_SLOTS_LEFT >= 2)
+			{
+				self->monsterinfo.attack_state = AS_BLIND;
+				return true;
+			}
+				
+			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
+			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
+				return false;
+		}
+	}
+
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw2(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+	// melee attack
+	if (self->timestamp < level.time)
+	{
+		real_enemy_range = realrange (self, self->enemy);
+		if (real_enemy_range < 300)
+		{
+			AngleVectors (self->s.angles, f, r, u);
+			G_ProjectSource2 (self->s.origin, offsets[0], f, r, u, spot1);
+			VectorCopy (self->enemy->s.origin, spot2);
+			if (widow2_tongue_attack_ok(spot1, spot2, 256))
+			{
+				// melee attack ok
+
+				// be nice in easy mode
+				if (skill->value == 0 && (rand()&3) )
+					return false;
+
+				if (self->monsterinfo.melee)
+					self->monsterinfo.attack_state = AS_MELEE;
+				else
+					self->monsterinfo.attack_state = AS_MISSILE;
+				return true;
+			}
+		}
+	}
+	
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_FAR)
+	{
+		chance = 0.5;
+	}
+
+	// PGM - go ahead and shoot every time if it's a info_notnull
+	if ((qrandom () < chance) || (self->enemy->solid == SOLID_NOT))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+//		self->monsterinfo.attack_finished = level.time + 1.0 + 2*random();
+		return true;
+	}
+
+	return false;
+}
+
+void Widow2Precache (void)
+{
+	// cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs
+	gi.soundindex ("parasite/parpain1.wav");	
+	gi.soundindex ("parasite/parpain2.wav");	
+	gi.soundindex ("parasite/pardeth1.wav");	
+	gi.soundindex ("parasite/paratck1.wav");
+	gi.soundindex ("parasite/parsght1.wav");
+	gi.soundindex ("infantry/melee2.wav");
+	gi.soundindex ("misc/fhit3.wav");
+
+	gi.soundindex ("tank/tnkatck3.wav");
+	gi.soundindex ("weapons/disrupt.wav");
+	gi.soundindex ("weapons/disint2.wav");
+
+	gi.modelindex ("models/monsters/stalker/tris.md2");
+	gi.modelindex ("models/items/spawngro2/tris.md2");
+	gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
+	gi.modelindex ("models/proj/laser2/tris.md2");
+	gi.modelindex ("models/proj/disintegrator/tris.md2");
+
+	gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2");
+	gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2");
+}
+
+/*QUAKED monster_widow2 (1 .5 0) (-70 -70 0) (70 70 144) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_widow2 (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("widow/bw2pain1.wav");
+	sound_pain2 = gi.soundindex ("widow/bw2pain2.wav");
+	sound_pain3 = gi.soundindex ("widow/bw2pain3.wav");
+	sound_death = gi.soundindex ("widow/death.wav");
+	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
+//	sound_disrupt = gi.soundindex ("gladiator/railgun.wav");
+	sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav");
+
+//	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/blackwidow2/tris.md2");
+	VectorSet (self->mins, -70, -70, 0);
+	VectorSet (self->maxs, 70, 70, 144);
+
+	self->health = 2000 + 800 + 1000*(skill->value);
+	if (coop->value)
+		self->health += 500*(skill->value);
+//	self->health = 1;
+	self->gib_health = -900;
+	self->mass = 2500;
+
+/*	if (skill->value == 2)
+	{
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+		self->monsterinfo.power_armor_power = 500;
+	}
+	else */if (skill->value == 3)
+	{
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+		self->monsterinfo.power_armor_power = 750;
+	}
+
+	self->yaw_speed = 30;
+	
+	self->flags |= FL_IMMUNE_LASER;
+	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
+
+	self->pain = widow2_pain;
+	self->die = widow2_die;
+
+	self->monsterinfo.melee = widow2_melee;
+	self->monsterinfo.stand = widow2_stand;
+	self->monsterinfo.walk = widow2_walk;
+	self->monsterinfo.run = widow2_run;
+	self->monsterinfo.attack = widow2_attack;
+	self->monsterinfo.search = widow2_search;
+	self->monsterinfo.checkattack = Widow2_CheckAttack;
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &widow2_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	Widow2Precache();
+	WidowCalcSlots(self);
+	walkmonster_start (self);
+}
+
+//
+// Death sequence stuff
+//
+
+void WidowVelocityForDamage (int damage, vec3_t v)
+{
+	v[0] = damage * crandom();
+	v[1] = damage * crandom();
+	v[2] = damage * crandom() + 200.0;
+}
+
+void widow_gib_touch (edict_t *self, edict_t *, cplane_t *, csurface_t *)
+{
+
+	self->solid = SOLID_NOT;
+	self->touch = NULL;
+	self->s.angles[PITCH] = 0;
+	self->s.angles[ROLL] = 0;
+	VectorClear (self->avelocity);
+
+	if (self->plat2flags)
+		gi.sound (self, CHAN_VOICE, self->plat2flags, 1, ATTN_NORM, 0);
+/*
+	if (plane)
+	{
+		if (plane->normal[2] < -0.8)
+		{
+			gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+		}
+		
+		//vectoangles (plane->normal, normal_angles);
+		//AngleVectors (normal_angles, NULL, right, NULL);
+		//vectoangles (right, self->s.angles);
+		//VectorClear (self->avelocity);
+	}
+*/
+}
+
+void ThrowWidowGib (edict_t *self, char *gibname, int damage, int type)
+{
+	ThrowWidowGibReal (self, gibname, damage, type, NULL, false, 0, true);
+}
+
+void ThrowWidowGibLoc (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean fade)
+{
+	ThrowWidowGibReal (self, gibname, damage, type, startpos, false, 0, fade);
+}
+
+void ThrowWidowGibSized (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, int hitsound, qboolean fade)
+{
+	ThrowWidowGibReal (self, gibname, damage, type, startpos, true, hitsound, fade);
+}
+
+void ThrowWidowGibReal (edict_t *self, char *gibname, int damage, int type, vec3_t startpos, qboolean sized, int hitsound, qboolean fade)
+{
+	edict_t *gib;
+	vec3_t	vd;
+	vec3_t	origin;
+	vec3_t	size;
+	float	vscale;
+
+	if (!gibname)
+		return;
+
+	gib = G_Spawn();
+
+	if (startpos)
+		VectorCopy (startpos, gib->s.origin);
+	else
+	{
+		VectorScale (self->size, 0.5, size);
+		VectorAdd (self->absmin, size, origin);
+		gib->s.origin[0] = origin[0] + crandom() * size[0];
+		gib->s.origin[1] = origin[1] + crandom() * size[1];
+		gib->s.origin[2] = origin[2] + crandom() * size[2];
+	}
+
+	gib->solid = SOLID_NOT;
+	gib->s.effects |= EF_GIB;
+	gib->flags |= FL_NO_KNOCKBACK;
+	gib->takedamage = DAMAGE_YES;
+	gib->die = gib_die;
+	gib->s.renderfx |= RF_IR_VISIBLE;
+
+	if (fade)
+	{
+		gib->think = G_FreeEdict;
+		// sized gibs last longer
+		if (sized)
+			gib->nextthink = level.time + 20 + qrandom()*15;
+		else
+			gib->nextthink = level.time + 5 + qrandom()*10;
+	}
+	else
+	{
+		gib->think = G_FreeEdict;
+		// sized gibs last longer
+		if (sized)
+			gib->nextthink = level.time + 60 + qrandom()*15;
+		else
+			gib->nextthink = level.time + 25 + qrandom()*10;
+	}
+
+	if (type == GIB_ORGANIC)
+	{
+		gib->movetype = MOVETYPE_TOSS;
+		gib->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		gib->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	WidowVelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, gib->velocity);
+	ClipGibVelocity (gib);
+
+	gi.setmodel (gib, gibname);
+
+	if (sized)
+	{
+		gib->plat2flags = hitsound;
+		gib->solid = SOLID_BBOX;
+		gib->avelocity[0] = qrandom()*400;
+		gib->avelocity[1] = qrandom()*400;
+		gib->avelocity[2] = qrandom()*200;
+		if (gib->velocity[2] < 0)
+			gib->velocity[2] *= -1;
+		gib->velocity[0] *= 2;
+		gib->velocity[1] *= 2;
+		ClipGibVelocity (gib);
+		gib->velocity[2] = qmax((350 + (qrandom()*100.0)), gib->velocity[2]);
+		gib->gravity = 0.25;
+		gib->touch = widow_gib_touch;
+		gib->owner = self;
+		if (gib->s.modelindex == gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2"))
+		{
+			VectorSet (gib->mins, -10, -10, 0);
+			VectorSet (gib->maxs, 10, 10, 10);
+		}
+		else
+		{
+			VectorSet (gib->mins, -5, -5, 0);
+			VectorSet (gib->maxs, 5, 5, 5);
+		}
+	}
+	else
+	{
+		gib->velocity[0] *= 2;
+		gib->velocity[1] *= 2;
+		gib->avelocity[0] = qrandom()*600;
+		gib->avelocity[1] = qrandom()*600;
+		gib->avelocity[2] = qrandom()*600;
+	}
+
+//	gib->think = G_FreeEdict;
+//	gib->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (gib);
+}
+
+void BloodFountain (edict_t */*self*/, int /*number*/, vec3_t /*startpos*/, int /*damage*/)
+{
+	return;
+/*
+	int n;
+	vec3_t	vd;
+	vec3_t	origin, size, velocity;
+
+	for (n= 0; n < number; n++)
+		if (startpos)
+			VectorCopy (startpos, origin);
+		else
+		{
+			VectorScale (self->size, 0.5, size);
+			VectorAdd (self->absmin, size, origin);
+			origin[0] = origin[0] + crandom() * size[0];
+			origin[1] = origin[1] + crandom() * size[1];
+			origin[2] = origin[2] + crandom() * size[2];
+		}
+
+		WidowVelocityForDamage (damage, vd);
+		VectorMA (self->velocity, 1.0, vd, velocity);
+		velocity[0] *= 2;
+		velocity[1] *= 2;
+
+//		gi.WriteByte (svc_temp_entity);
+//		gi.WriteByte (TE_BLOOD_FOUNTAIN);
+//		gi.WritePosition (origin);
+//		gi.WritePosition (velocity);
+//		gi.WriteShort (50);
+//		gi.multicast (self->s.origin, MULTICAST_ALL);
+	}
+*/
+}
+
+void ThrowSmallStuff (edict_t *self, vec3_t point)
+{
+	int n;
+
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point, false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point, false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point, false);
+
+}
+
+void ThrowMoreStuff (edict_t *self, vec3_t point)
+{
+	int n;
+
+	if (coop && coop->value)
+	{
+		ThrowSmallStuff (self, point);
+		return;
+	}
+
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, point, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, point, false);
+	for (n= 0; n < 3; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, point, false);
+
+}
+
+void WidowExplode (edict_t *self)
+{
+	vec3_t	org;
+	int		n;
+
+	self->think = WidowExplode;
+//	gi.dprintf ("count = %d\n");
+
+//redo:
+	VectorCopy (self->s.origin, org);
+	org[2] += 24 + (rand()&15);
+	if (self->count < 8)
+		org[2] += 24 + (rand()&31);
+	switch (self->count)
+	{
+	case 0:
+		org[0] -= 24;
+		org[1] -= 24;
+		break;
+	case 1:
+		org[0] += 24;
+		org[1] += 24;
+		ThrowSmallStuff(self, org);
+		break;
+	case 2:
+		org[0] += 24;
+		org[1] -= 24;
+		break;
+	case 3:
+		org[0] -= 24;
+		org[1] += 24;
+		ThrowMoreStuff(self, org);
+		break;
+	case 4:
+		org[0] -= 48;
+		org[1] -= 48;
+		break;
+	case 5:
+		org[0] += 48;
+		org[1] += 48;
+		ThrowArm1 (self);
+		break;
+	case 6:
+		org[0] -= 48;
+		org[1] += 48;
+		ThrowArm2 (self);
+		break;
+	case 7:
+		org[0] += 48;
+		org[1] -= 48;
+		ThrowSmallStuff(self, org);
+		break;
+	case 8:
+		org[0] += 18;
+		org[1] += 18;
+		org[2] = self->s.origin[2] + 48;
+		ThrowMoreStuff(self, org);
+		break;
+	case 9:
+		org[0] -= 18;
+		org[1] += 18;
+		org[2] = self->s.origin[2] + 48;
+		break;
+	case 10:
+		org[0] += 18;
+		org[1] -= 18;
+		org[2] = self->s.origin[2] + 48;
+		break;
+	case 11:
+		org[0] -= 18;
+		org[1] -= 18;
+		org[2] = self->s.origin[2] + 48;
+		break;
+	case 12:
+		self->s.sound = 0;
+		for (n= 0; n < 1; n++)
+			ThrowWidowGib (self, "models/objects/gibs/sm_meat/tris.md2", 400, GIB_ORGANIC);
+		for (n= 0; n < 2; n++)
+			ThrowWidowGib (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC);
+		for (n= 0; n < 2; n++)
+			ThrowWidowGib (self, "models/objects/gibs/sm_metal/tris.md2", 400, GIB_METALLIC);
+//		ThrowGib (self, "models/objects/gibs/chest/tris.md2", 1000, GIB_ORGANIC);
+//		ThrowHead (self, "models/objects/gibs/gear/tris.md2", 1000, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		self->think = monster_think;
+		self->nextthink = level.time + 0.1;
+		self->monsterinfo.currentmove = &widow2_move_dead;
+		return;
+	}
+
+	self->count++;
+	if (self->count >=9 && self->count <=12)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_EXPLOSION1_BIG);
+		gi.WritePosition (org);
+		gi.multicast (self->s.origin, MULTICAST_ALL);
+//		goto redo;
+	} 
+	else
+	{
+		// else
+		gi.WriteByte (svc_temp_entity);
+		if (self->count %2)
+			gi.WriteByte (TE_EXPLOSION1);
+		else
+			gi.WriteByte (TE_EXPLOSION1_NP);
+		gi.WritePosition (org);
+		gi.multicast (self->s.origin, MULTICAST_ALL);
+	}
+
+	self->nextthink = level.time + 0.1;
+}
+
+void WidowExplosion1 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {23.74, -37.67, 76.96};
+
+//	gi.dprintf ("1\n");
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion2 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {-20.49, 36.92, 73.52};
+
+//	gi.dprintf ("2\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion3 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {2.11, 0.05, 92.20};
+
+//	gi.dprintf ("3\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion4 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {-28.04, -35.57, -77.56};
+
+//	gi.dprintf ("4\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion5 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {-20.11, -1.11, 40.76};
+
+//	gi.dprintf ("5\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion6 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {-20.11, -1.11, 40.76};
+
+	//gi.dprintf ("6\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosion7 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset = {-20.11, -1.11, 40.76};
+
+	//gi.dprintf ("7\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+	
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	for (n= 0; n < 1; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 300, GIB_METALLIC, startpoint, false);
+}
+
+void WidowExplosionLeg (edict_t *self)
+{
+//	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset1 = {-31.89, -47.86, 67.02};
+	vec3_t	offset2 = {-44.9, -82.14, 54.72};
+
+	//gi.dprintf ("Leg\n");
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1_BIG);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+
+	ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib2/tris.md2", 200, GIB_METALLIC, startpoint, 
+		gi.soundindex ("misc/fhit3.wav"), false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+
+	G_ProjectSource2 (self->s.origin, offset2, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+
+	ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib1/tris.md2", 300, GIB_METALLIC, startpoint,
+		gi.soundindex ("misc/fhit3.wav"), false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+}
+
+void ThrowArm1 (edict_t *self)
+{
+	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset1 = {65.76, 17.52, 7.56};
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1_BIG);
+	gi.WritePosition (startpoint);
+	gi.multicast (self->s.origin, MULTICAST_ALL);
+
+	for (n= 0; n < 2; n++)
+		ThrowWidowGibLoc (self, "models/objects/gibs/sm_metal/tris.md2", 100, GIB_METALLIC, startpoint, false);
+}
+
+void ThrowArm2 (edict_t *self)
+{
+//	int		n;
+	vec3_t	f,r,u, startpoint;
+	vec3_t	offset1 = {65.76, 17.52, 7.56};
+
+	AngleVectors (self->s.angles, f, r, u);
+	G_ProjectSource2 (self->s.origin, offset1, f, r, u, startpoint);
+
+	ThrowWidowGibSized (self, "models/monsters/blackwidow2/gib4/tris.md2", 200, GIB_METALLIC, startpoint, 
+		gi.soundindex ("misc/fhit3.wav"), false);
+	ThrowWidowGibLoc (self, "models/objects/gibs/sm_meat/tris.md2", 300, GIB_ORGANIC, startpoint, false);
+}
--- /dev/null
+++ b/rogue/m_widow2.h
@@ -1,0 +1,132 @@
+// G:\quake2\xpack\models/monsters/blackwidow2
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_blackwidow3     	0
+#define FRAME_walk01          	1
+#define FRAME_walk02          	2
+#define FRAME_walk03          	3
+#define FRAME_walk04          	4
+#define FRAME_walk05          	5
+#define FRAME_walk06          	6
+#define FRAME_walk07          	7
+#define FRAME_walk08          	8
+#define FRAME_walk09          	9
+#define FRAME_spawn01         	10
+#define FRAME_spawn02         	11
+#define FRAME_spawn03         	12
+#define FRAME_spawn04         	13
+#define FRAME_spawn05         	14
+#define FRAME_spawn06         	15
+#define FRAME_spawn07         	16
+#define FRAME_spawn08         	17
+#define FRAME_spawn09         	18
+#define FRAME_spawn10         	19
+#define FRAME_spawn11         	20
+#define FRAME_spawn12         	21
+#define FRAME_spawn13         	22
+#define FRAME_spawn14         	23
+#define FRAME_spawn15         	24
+#define FRAME_spawn16         	25
+#define FRAME_spawn17         	26
+#define FRAME_spawn18         	27
+#define FRAME_firea01         	28
+#define FRAME_firea02         	29
+#define FRAME_firea03         	30
+#define FRAME_firea04         	31
+#define FRAME_firea05         	32
+#define FRAME_firea06         	33
+#define FRAME_firea07         	34
+#define FRAME_fireb01         	35
+#define FRAME_fireb02         	36
+#define FRAME_fireb03         	37
+#define FRAME_fireb04         	38
+#define FRAME_fireb05         	39
+#define FRAME_fireb06         	40
+#define FRAME_fireb07         	41
+#define FRAME_fireb08         	42
+#define FRAME_fireb09         	43
+#define FRAME_fireb10         	44
+#define FRAME_fireb11         	45
+#define FRAME_fireb12         	46
+#define FRAME_tongs01         	47
+#define FRAME_tongs02         	48
+#define FRAME_tongs03         	49
+#define FRAME_tongs04         	50
+#define FRAME_tongs05         	51
+#define FRAME_tongs06         	52
+#define FRAME_tongs07         	53
+#define FRAME_tongs08         	54
+#define FRAME_pain01          	55
+#define FRAME_pain02          	56
+#define FRAME_pain03          	57
+#define FRAME_pain04          	58
+#define FRAME_pain05          	59
+#define FRAME_death01         	60
+#define FRAME_death02         	61
+#define FRAME_death03         	62
+#define FRAME_death04         	63
+#define FRAME_death05         	64
+#define FRAME_death06         	65
+#define FRAME_death07         	66
+#define FRAME_death08         	67
+#define FRAME_death09         	68
+#define FRAME_death10         	69
+#define FRAME_death11         	70
+#define FRAME_death12         	71
+#define FRAME_death13         	72
+#define FRAME_death14         	73
+#define FRAME_death15         	74
+#define FRAME_death16         	75
+#define FRAME_death17         	76
+#define FRAME_death18         	77
+#define FRAME_death19         	78
+#define FRAME_death20         	79
+#define FRAME_death21         	80
+#define FRAME_death22         	81
+#define FRAME_death23         	82
+#define FRAME_death24         	83
+#define FRAME_death25         	84
+#define FRAME_death26         	85
+#define FRAME_death27         	86
+#define FRAME_death28         	87
+#define FRAME_death29         	88
+#define FRAME_death30         	89
+#define FRAME_death31         	90
+#define FRAME_death32         	91
+#define FRAME_death33         	92
+#define FRAME_death34         	93
+#define FRAME_death35         	94
+#define FRAME_death36         	95
+#define FRAME_death37         	96
+#define FRAME_death38         	97
+#define FRAME_death39         	98
+#define FRAME_death40         	99
+#define FRAME_death41         	100
+#define FRAME_death42         	101
+#define FRAME_death43         	102
+#define FRAME_death44         	103
+#define FRAME_dthsrh01        	104
+#define FRAME_dthsrh02        	105
+#define FRAME_dthsrh03        	106
+#define FRAME_dthsrh04        	107
+#define FRAME_dthsrh05        	108
+#define FRAME_dthsrh06        	109
+#define FRAME_dthsrh07        	110
+#define FRAME_dthsrh08        	111
+#define FRAME_dthsrh09        	112
+#define FRAME_dthsrh10        	113
+#define FRAME_dthsrh11        	114
+#define FRAME_dthsrh12        	115
+#define FRAME_dthsrh13        	116
+#define FRAME_dthsrh14        	117
+#define FRAME_dthsrh15        	118
+#define FRAME_dthsrh16        	119
+#define FRAME_dthsrh17        	120
+#define FRAME_dthsrh18        	121
+#define FRAME_dthsrh19        	122
+#define FRAME_dthsrh20        	123
+#define FRAME_dthsrh21        	124
+#define FRAME_dthsrh22        	125
+
+#define MODEL_SCALE		2.000000
--- /dev/null
+++ b/rogue/mkfile
@@ -1,0 +1,100 @@
+</$objtype/mkfile
+
+LIB=rogue.$O.a
+
+OFILES=\
+	dm_ball.$O\
+	dm_tag.$O\
+	g_ai.$O\
+	g_chase.$O\
+	g_cmds.$O\
+	g_combat.$O\
+	g_func.$O\
+	g_items.$O\
+	g_main.$O\
+	g_misc.$O\
+	g_monster.$O\
+	g_newai.$O\
+	g_newdm.$O\
+	g_newfnc.$O\
+	g_newtarg.$O\
+	g_newtrig.$O\
+	g_newweap.$O\
+	g_phys.$O\
+	g_save.$O\
+	g_spawn.$O\
+	g_sphere.$O\
+	g_svcmds.$O\
+	g_target.$O\
+	g_trigger.$O\
+	g_turret.$O\
+	g_utils.$O\
+	g_weapon.$O\
+	m_actor.$O\
+	m_berserk.$O\
+	m_boss2.$O\
+	m_boss3.$O\
+	m_boss31.$O\
+	m_boss32.$O\
+	m_brain.$O\
+	m_carrier.$O\
+	m_chick.$O\
+	m_flash.$O\
+	m_flipper.$O\
+	m_float.$O\
+	m_flyer.$O\
+	m_gladiator.$O\
+	m_gunner.$O\
+	m_hover.$O\
+	m_infantry.$O\
+	m_insane.$O\
+	m_medic.$O\
+	m_move.$O\
+	m_mutant.$O\
+	m_parasite.$O\
+	m_soldier.$O\
+	m_stalker.$O\
+	m_supertank.$O\
+	m_tank.$O\
+	m_turret.$O\
+	m_widow.$O\
+	m_widow2.$O\
+	p_client.$O\
+	p_hud.$O\
+	p_trail.$O\
+	p_view.$O\
+	p_weapon.$O\
+	q_shared.$O\
+
+HFILES=\
+	game.h\
+	m_actor.h\
+	m_berserk.h\
+	m_boss2.h\
+	m_boss31.h\
+	m_boss32.h\
+	m_brain.h\
+	m_carrier.h\
+	m_chick.h\
+	m_flipper.h\
+	m_float.h\
+	m_flyer.h\
+	m_gladiator.h\
+	m_gunner.h\
+	m_hover.h\
+	m_infantry.h\
+	m_insane.h\
+	m_medic.h\
+	m_mutant.h\
+	m_parasite.h\
+	m_player.h\
+	m_rider.h\
+	m_soldier.h\
+	m_stalker.h\
+	m_supertank.h\
+	m_tank.h\
+	m_turret.h\
+	m_widow.h\
+	m_widow2.h\
+
+</sys/src/cmd/mklib
--- /dev/null
+++ b/rogue/p_client.c
@@ -1,0 +1,2197 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+
+void SP_misc_teleporter_dest (edict_t *ent);
+
+//
+// Gross, ugly, disgustuing hack section
+//
+
+// this function is an ugly as hell hack to fix some map flaws
+//
+// the coop spawn spots on some maps are SNAFU.  There are coop spots
+// with the wrong targetname as well as spots with no name at all
+//
+// we use carnal knowledge of the maps to fix the coop spot targetnames to match
+// that of the nearest named single player spot
+
+static void SP_FixCoopSpots (edict_t *self)
+{
+	edict_t	*spot;
+	vec3_t	d;
+
+	spot = NULL;
+
+	while(1)
+	{
+		spot = G_Find(spot, FOFS(classname), "info_player_start");
+		if (!spot)
+			return;
+		if (!spot->targetname)
+			continue;
+		VectorSubtract(self->s.origin, spot->s.origin, d);
+		if (VectorLength(d) < 384)
+		{
+			if ((!self->targetname) || cistrcmp(self->targetname, spot->targetname) != 0)
+			{
+//				gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname);
+				self->targetname = spot->targetname;
+			}
+			return;
+		}
+	}
+}
+
+// now if that one wasn't ugly enough for you then try this one on for size
+// some maps don't have any coop spots at all, so we need to create them
+// where they should have been
+
+static void SP_CreateCoopSpots (edict_t *)
+{
+	edict_t	*spot;
+
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 - 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 128;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		return;
+	}
+}
+
+
+/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+The normal starting point for a level.
+*/
+void SP_info_player_start(edict_t *self)
+{
+	if (!coop->value)
+		return;
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_CreateCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for deathmatch games
+*/
+void SP_info_player_deathmatch(edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	SP_misc_teleporter_dest (self);
+}
+
+/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for coop games
+*/
+
+void SP_info_player_coop(edict_t *self)
+{
+	if (!coop->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if((cistrcmp(level.mapname, "jail2") == 0)   ||
+	   (cistrcmp(level.mapname, "jail4") == 0)   ||
+	   (cistrcmp(level.mapname, "mine1") == 0)   ||
+	   (cistrcmp(level.mapname, "mine2") == 0)   ||
+	   (cistrcmp(level.mapname, "mine3") == 0)   ||
+	   (cistrcmp(level.mapname, "mine4") == 0)   ||
+	   (cistrcmp(level.mapname, "lab") == 0)     ||
+	   (cistrcmp(level.mapname, "boss1") == 0)   ||
+	   (cistrcmp(level.mapname, "fact3") == 0)   ||
+	   (cistrcmp(level.mapname, "biggun") == 0)  ||
+	   (cistrcmp(level.mapname, "space") == 0)   ||
+	   (cistrcmp(level.mapname, "command") == 0) ||
+	   (cistrcmp(level.mapname, "power2") == 0) ||
+	   (cistrcmp(level.mapname, "strike") == 0))
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_FixCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+/*QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for coop games on rmine2 where lava level
+needs to be checked
+*/
+void SP_info_player_coop_lava(edict_t *self)
+{
+	if (!coop->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+}
+
+/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The deathmatch intermission point will be at one of these
+Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw.  'pitch yaw roll'
+*/
+void SP_info_player_intermission(edict_t *)
+{
+}
+
+
+//=======================================================================
+
+
+void player_pain (edict_t *, edict_t *, float, int)
+{
+	// player pain is handled at the end of the frame in P_DamageFeedback
+}
+
+
+qboolean IsFemale (edict_t *ent)
+{
+	char		*info;
+
+	if (!ent->client)
+		return false;
+
+	info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+	if (info[0] == 'f' || info[0] == 'F')
+		return true;
+	return false;
+}
+
+qboolean IsNeutral (edict_t *ent)
+{
+	char		*info;
+
+	if (!ent->client)
+		return false;
+
+	info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+	if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
+		return true;
+	return false;
+}
+
+void ClientObituary (edict_t *self, edict_t *, edict_t *attacker)
+{
+	int			mod;
+	char		*message;
+	char		*message2;
+	qboolean	ff;
+
+	if (coop->value && attacker->client)
+		meansOfDeath |= MOD_FRIENDLY_FIRE;
+
+	if (deathmatch->value || coop->value)
+	{
+		ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+		mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+		message = NULL;
+		message2 = "";
+
+		switch (mod)
+		{
+			case MOD_SUICIDE:
+				message = "suicides";
+				break;
+			case MOD_FALLING:
+				message = "cratered";
+				break;
+			case MOD_CRUSH:
+				message = "was squished";
+				break;
+			case MOD_WATER:
+				message = "sank like a rock";
+				break;
+			case MOD_SLIME:
+				message = "melted";
+				break;
+			case MOD_LAVA:
+				message = "does a back flip into the lava";
+				break;
+			case MOD_EXPLOSIVE:
+			case MOD_BARREL:
+				message = "blew up";
+				break;
+			case MOD_EXIT:
+				message = "found a way out";
+				break;
+			case MOD_TARGET_LASER:
+				message = "saw the light";
+				break;
+			case MOD_TARGET_BLASTER:
+				message = "got blasted";
+				break;
+			case MOD_BOMB:
+			case MOD_SPLASH:
+			case MOD_TRIGGER_HURT:
+				message = "was in the wrong place";
+				break;
+		}
+		if (attacker == self)
+		{
+			switch (mod)
+			{
+				case MOD_HELD_GRENADE:
+					message = "tried to put the pin back in";
+					break;
+				case MOD_HG_SPLASH:
+				case MOD_G_SPLASH:
+					if (IsNeutral(self))
+						message = "tripped on its own grenade";
+					else if (IsFemale(self))
+						message = "tripped on her own grenade";
+					else
+						message = "tripped on his own grenade";
+					break;
+				case MOD_R_SPLASH:
+					if (IsNeutral(self))
+						message = "blew itself up";
+					else if (IsFemale(self))
+						message = "blew herself up";
+					else
+						message = "blew himself up";
+					break;
+				case MOD_BFG_BLAST:
+					message = "should have used a smaller gun";
+					break;
+//ROGUE
+				case MOD_DOPPLE_EXPLODE:
+					if (IsNeutral(self))
+						message = "got caught in it's own trap";
+					else if (IsFemale(self))
+						message = "got caught in her own trap";
+					else
+						message = "got caught in his own trap";
+					break;
+//ROGUE
+				default:
+					if (IsNeutral(self))
+						message = "killed itself";
+					else if (IsFemale(self))
+						message = "killed herself";
+					else
+						message = "killed himself";
+					break;
+			}
+		}
+		if (message)
+		{
+			gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
+			if (deathmatch->value)
+				self->client->resp.score--;
+			self->enemy = NULL;
+			return;
+		}
+
+		self->enemy = attacker;
+		if (attacker && attacker->client)
+		{
+			switch (mod)
+			{
+			case MOD_BLASTER:
+				message = "was blasted by";
+				break;
+			case MOD_SHOTGUN:
+				message = "was gunned down by";
+				break;
+			case MOD_SSHOTGUN:
+				message = "was blown away by";
+				message2 = "'s super shotgun";
+				break;
+			case MOD_MACHINEGUN:
+				message = "was machinegunned by";
+				break;
+			case MOD_CHAINGUN:
+				message = "was cut in half by";
+				message2 = "'s chaingun";
+				break;
+			case MOD_GRENADE:
+				message = "was popped by";
+				message2 = "'s grenade";
+				break;
+			case MOD_G_SPLASH:
+				message = "was shredded by";
+				message2 = "'s shrapnel";
+				break;
+			case MOD_ROCKET:
+				message = "ate";
+				message2 = "'s rocket";
+				break;
+			case MOD_R_SPLASH:
+				message = "almost dodged";
+				message2 = "'s rocket";
+				break;
+			case MOD_HYPERBLASTER:
+				message = "was melted by";
+				message2 = "'s hyperblaster";
+				break;
+			case MOD_RAILGUN:
+				message = "was railed by";
+				break;
+			case MOD_BFG_LASER:
+				message = "saw the pretty lights from";
+				message2 = "'s BFG";
+				break;
+			case MOD_BFG_BLAST:
+				message = "was disintegrated by";
+				message2 = "'s BFG blast";
+				break;
+			case MOD_BFG_EFFECT:
+				message = "couldn't hide from";
+				message2 = "'s BFG";
+				break;
+			case MOD_HANDGRENADE:
+				message = "caught";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HG_SPLASH:
+				message = "didn't see";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HELD_GRENADE:
+				message = "feels";
+				message2 = "'s pain";
+				break;
+			case MOD_TELEFRAG:
+				message = "tried to invade";
+				message2 = "'s personal space";
+				break;
+
+//===============
+//ROGUE
+			case MOD_CHAINFIST:
+				message = "was shredded by";
+				message2 = "'s ripsaw";
+				break;
+			case MOD_DISINTEGRATOR:
+				message = "lost his grip courtesy of";
+				message2 = "'s disintegrator";
+				break;
+			case MOD_ETF_RIFLE:
+				message = "was perforated by";
+				break;
+			case MOD_HEATBEAM:
+				message = "was scorched by";
+				message2 = "'s plasma beam";
+				break;
+			case MOD_TESLA:
+				message = "was enlightened by";
+				message2 = "'s tesla mine";
+				break;
+			case MOD_PROX:
+				message = "got too close to";
+				message2 = "'s proximity mine";
+				break;
+			case MOD_NUKE:
+				message = "was nuked by";
+				message2 = "'s antimatter bomb";
+				break;
+			case MOD_VENGEANCE_SPHERE:
+				message = "was purged by";
+				message2 = "'s vengeance sphere";
+				break;
+			case MOD_DEFENDER_SPHERE:
+				message = "had a blast with";
+				message2 = "'s defender sphere";
+				break;
+			case MOD_HUNTER_SPHERE:
+				message = "was killed like a dog by";
+				message2 = "'s hunter sphere";
+				break;
+			case MOD_TRACKER:
+				message = "was annihilated by";
+				message2 = "'s disruptor";
+				break;
+			case MOD_DOPPLE_EXPLODE:
+				message = "was blown up by";
+				message2 = "'s doppleganger";
+				break;
+			case MOD_DOPPLE_VENGEANCE:
+				message = "was purged by";
+				message2 = "'s doppleganger";
+				break;
+			case MOD_DOPPLE_HUNTER:
+				message = "was hunted down by";
+				message2 = "'s doppleganger";
+				break;
+//ROGUE
+//===============
+			}
+			if (message)
+			{
+				gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
+//ROGUE
+				if (gamerules && gamerules->value)
+				{
+					if(DMGame.Score)
+					{
+						if(ff)		
+							DMGame.Score(attacker, self, -1);
+						else
+							DMGame.Score(attacker, self, 1);
+					}
+					return;
+				}
+//ROGUE
+
+				if (deathmatch->value)
+				{
+					if (ff)
+						attacker->client->resp.score--;
+					else
+						attacker->client->resp.score++;
+				}
+				return;
+			}
+		}
+	}
+
+	gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
+
+//ROGUE
+//	if (g_showlogic && g_showlogic->value)
+//	{
+//		if (mod == MOD_UNKNOWN)
+//			gi.dprintf ("Player killed by MOD_UNKNOWN\n");
+//		else
+//			gi.dprintf ("Player killed by undefined mod %d\n", mod);
+//	}
+//ROGUE
+
+	if (deathmatch->value)
+//ROGUE
+	{
+		if (gamerules && gamerules->value)
+		{
+			if(DMGame.Score)
+			{
+				DMGame.Score(self, self, -1);
+			}
+			return;
+		}
+		else
+			self->client->resp.score--;
+	}
+//ROGUE
+
+}
+
+
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void TossClientWeapon (edict_t *self)
+{
+	gitem_t		*item;
+	edict_t		*drop;
+	qboolean	quad;
+	float		spread;
+
+	if (!deathmatch->value)
+		return;
+
+	item = self->client->pers.weapon;
+	if (! self->client->pers.inventory[self->client->ammo_index] )
+		item = NULL;
+	if (item && (strcmp (item->pickup_name, "Blaster") == 0))
+		item = NULL;
+
+	if (!((int)(dmflags->value) & DF_QUAD_DROP))
+		quad = false;
+	else
+		quad = (self->client->quad_framenum > (level.framenum + 10));
+
+	if (item && quad)
+		spread = 22.5;
+	else
+		spread = 0.0;
+
+	if (item)
+	{
+		self->client->v_angle[YAW] -= spread;
+		drop = Drop_Item (self, item);
+		self->client->v_angle[YAW] += spread;
+		drop->spawnflags = DROPPED_PLAYER_ITEM;
+	}
+
+	if (quad)
+	{
+		self->client->v_angle[YAW] += spread;
+		drop = Drop_Item (self, FindItemByClassname ("item_quad"));
+		self->client->v_angle[YAW] -= spread;
+		drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+		drop->touch = Touch_Item;
+		drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
+		drop->think = G_FreeEdict;
+	}
+}
+
+
+/*
+==================
+LookAtKiller
+==================
+*/
+void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+	vec3_t		dir;
+
+	if (attacker && attacker != WORLD && attacker != self)
+	{
+		VectorSubtract (attacker->s.origin, self->s.origin, dir);
+	}
+	else if (inflictor && inflictor != WORLD && inflictor != self)
+	{
+		VectorSubtract (inflictor->s.origin, self->s.origin, dir);
+	}
+	else
+	{
+		self->client->killer_yaw = self->s.angles[YAW];
+		return;
+	}
+	// PMM - fixed to correct for pitch of 0
+	if (dir[0])
+		self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
+	else if (dir[1] > 0)
+		self->client->killer_yaw = 90;
+	else if (dir[1] < 0)
+		self->client->killer_yaw = 270;
+	else
+		self->client->killer_yaw = 0;
+}
+
+/*
+==================
+player_die
+==================
+*/
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	int		n;
+
+	VectorClear (self->avelocity);
+
+	self->takedamage = DAMAGE_YES;
+	self->movetype = MOVETYPE_TOSS;
+
+	self->s.modelindex2 = 0;	// remove linked weapon model
+
+	self->s.angles[0] = 0;
+	self->s.angles[2] = 0;
+
+	self->s.sound = 0;
+	self->client->weapon_sound = 0;
+
+	self->maxs[2] = -8;
+
+//	self->solid = SOLID_NOT;
+	self->svflags |= SVF_DEADMONSTER;
+
+	if (!self->deadflag)
+	{
+		self->client->respawn_time = level.time + 1.0;
+		LookAtKiller (self, inflictor, attacker);
+		self->client->ps.pmove.pm_type = PM_DEAD;
+		ClientObituary (self, inflictor, attacker);
+		TossClientWeapon (self);
+		if (deathmatch->value)
+			Cmd_Help_f (self);		// show scores
+
+		// clear inventory
+		// this is kind of ugly, but it's how we want to handle keys in coop
+		for (n = 0; n < game.num_items; n++)
+		{
+			if (coop->value && itemlist[n].flags & IT_KEY)
+				self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
+			self->client->pers.inventory[n] = 0;
+		}
+	}
+
+	if(gamerules && gamerules->value)	// if we're in a dm game, alert the game
+	{
+		if(DMGame.PlayerDeath)
+			DMGame.PlayerDeath(self, inflictor, attacker);
+	}
+
+	// remove powerups
+	self->client->quad_framenum = 0;
+	self->client->invincible_framenum = 0;
+	self->client->breather_framenum = 0;
+	self->client->enviro_framenum = 0;
+	self->flags &= ~FL_POWER_ARMOR;
+
+//==============
+// ROGUE stuff
+	self->client->double_framenum = 0;
+
+	// if there's a sphere around, let it know the player died.
+	// vengeance and hunter will die if they're not attacking,
+	// defender should always die
+	if(self->client->owned_sphere)
+	{
+		edict_t *sphere;
+
+		sphere = self->client->owned_sphere;
+		sphere->die(sphere, self, self, 0, vec3_origin);
+	}
+
+	// if we've been killed by the tracker, GIB!
+	if((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER)
+	{
+		self->health = -100;
+		damage = 400;
+	}
+
+	// make sure no trackers are still hurting us.
+	if(self->client->tracker_pain_framenum)
+	{
+		RemoveAttackingPainDaemons (self);
+	}
+	
+	// if we got obliterated by the nuke, don't gib
+	if ((self->health < -80) && (meansOfDeath == MOD_NUKE))
+		self->flags |= FL_NOGIB;
+
+// ROGUE
+//==============
+
+	if (self->health < -40)
+	{
+		// PMM
+		// don't toss gibs if we got vaped by the nuke
+		if (!(self->flags & FL_NOGIB))
+		{
+		// pmm
+			// gib
+			gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+			
+			// more meaty gibs for your dollar!
+			if((deathmatch->value) && (self->health < -80))
+			{
+				for (n= 0; n < 4; n++)
+					ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+			}
+
+			for (n= 0; n < 4; n++)
+				ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		// PMM	
+		}
+		self->flags &= ~FL_NOGIB;
+		// pmm
+
+		ThrowClientHead (self, damage);
+
+		self->takedamage = DAMAGE_NO;
+	}
+	else
+	{	// normal death
+		if (!self->deadflag)
+		{
+			static int i;
+
+			i = (i+1)%3;
+			// start a death animation
+			self->client->anim_priority = ANIM_DEATH;
+			if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				self->s.frame = FRAME_crdeath1-1;
+				self->client->anim_end = FRAME_crdeath5;
+			}
+			else switch (i)
+			{
+			case 0:
+				self->s.frame = FRAME_death101-1;
+				self->client->anim_end = FRAME_death106;
+				break;
+			case 1:
+				self->s.frame = FRAME_death201-1;
+				self->client->anim_end = FRAME_death206;
+				break;
+			case 2:
+				self->s.frame = FRAME_death301-1;
+				self->client->anim_end = FRAME_death308;
+				break;
+			}
+			gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
+		}
+	}
+
+	self->deadflag = DEAD_DEAD;
+
+	gi.linkentity (self);
+}
+
+//=======================================================================
+
+/*
+==============
+InitClientPersistant
+
+This is only called when the game first initializes in single player,
+but is called after each death and level change in deathmatch
+==============
+*/
+void InitClientPersistant (gclient_t *client)
+{
+	gitem_t		*item;
+
+	memset (&client->pers, 0, sizeof(client->pers));
+
+	item = FindItem("Blaster");
+	client->pers.selected_item = ITEM_INDEX(item);
+	client->pers.inventory[client->pers.selected_item] = 1;
+
+	client->pers.weapon = item;
+
+	client->pers.health			= 100;
+	client->pers.max_health		= 100;
+
+	client->pers.max_bullets	= 200;
+	client->pers.max_shells		= 100;
+	client->pers.max_rockets	= 50;
+	client->pers.max_grenades	= 50;
+	client->pers.max_cells		= 200;
+	client->pers.max_slugs		= 50;
+
+//ROGUE
+	// FIXME - give these real numbers....
+	client->pers.max_prox		= 50;
+	client->pers.max_tesla		= 50;
+	client->pers.max_flechettes = 200;
+#ifndef KILL_DISRUPTOR
+	client->pers.max_rounds     = 100;
+#endif
+//ROGUE
+
+	client->pers.connected = true;
+}
+
+
+void InitClientResp (gclient_t *client)
+{
+	memset (&client->resp, 0, sizeof(client->resp));
+	client->resp.enterframe = level.framenum;
+	client->resp.coop_respawn = client->pers;
+}
+
+/*
+==================
+SaveClientData
+
+Some information that should be persistant, like health, 
+is still stored in the edict structure, so it needs to
+be mirrored out to the client structure before all the
+edicts are wiped.
+==================
+*/
+void SaveClientData (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		ent = &g_edicts[1+i];
+		if (!ent->inuse)
+			continue;
+		game.clients[i].pers.health = ent->health;
+		game.clients[i].pers.max_health = ent->max_health;
+		game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR));
+		if (coop->value)
+			game.clients[i].pers.score = ent->client->resp.score;
+	}
+}
+
+void FetchClientEntData (edict_t *ent)
+{
+	ent->health = ent->client->pers.health;
+	ent->max_health = ent->client->pers.max_health;
+	ent->flags |= ent->client->pers.savedFlags;
+	if (coop->value)
+		ent->client->resp.score = ent->client->pers.score;
+}
+
+
+
+/*
+=======================================================================
+
+  SelectSpawnPoint
+
+=======================================================================
+*/
+
+/*
+================
+PlayersRangeFromSpot
+
+Returns the distance to the nearest player from the given spot
+================
+*/
+float	PlayersRangeFromSpot (edict_t *spot)
+{
+	edict_t	*player;
+	float	bestplayerdistance;
+	vec3_t	v;
+	int		n;
+	float	playerdistance;
+
+
+	bestplayerdistance = 9999999;
+
+	for (n = 1; n <= maxclients->value; n++)
+	{
+		player = &g_edicts[n];
+
+		if (!player->inuse)
+			continue;
+
+		if (player->health <= 0)
+			continue;
+
+		VectorSubtract (spot->s.origin, player->s.origin, v);
+		playerdistance = VectorLength (v);
+
+		if (playerdistance < bestplayerdistance)
+			bestplayerdistance = playerdistance;
+	}
+
+	return bestplayerdistance;
+}
+
+/*
+================
+SelectRandomDeathmatchSpawnPoint
+
+go to a random point, but NOT the two points closest
+to other players
+================
+*/
+edict_t *SelectRandomDeathmatchSpawnPoint (void)
+{
+	edict_t	*spot, *spot1, *spot2;
+	int		count = 0;
+	int		selection;
+	float	range, range1, range2;
+
+	spot = NULL;
+	range1 = range2 = 99999;
+	spot1 = spot2 = NULL;
+
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		count++;
+		range = PlayersRangeFromSpot(spot);
+		if (range < range1)
+		{
+			range1 = range;
+			spot1 = spot;
+		}
+		else if (range < range2)
+		{
+			range2 = range;
+			spot2 = spot;
+		}
+	}
+
+	if (!count)
+		return NULL;
+
+	if (count <= 2)
+	{
+		spot1 = spot2 = NULL;
+	}
+	else
+		count -= 2;
+
+	selection = rand() % count;
+
+	spot = NULL;
+	do
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+		if (spot == spot1 || spot == spot2)
+			selection++;
+	} while(selection--);
+
+	return spot;
+}
+
+/*
+================
+SelectFarthestDeathmatchSpawnPoint
+
+================
+*/
+edict_t *SelectFarthestDeathmatchSpawnPoint (void)
+{
+	edict_t	*bestspot;
+	float	bestdistance, bestplayerdistance;
+	edict_t	*spot;
+
+
+	spot = NULL;
+	bestspot = NULL;
+	bestdistance = 0;
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		bestplayerdistance = PlayersRangeFromSpot (spot);
+
+		if (bestplayerdistance > bestdistance)
+		{
+			bestspot = spot;
+			bestdistance = bestplayerdistance;
+		}
+	}
+
+	if (bestspot)
+	{
+		return bestspot;
+	}
+
+	// if there is a player just spawned on each and every start spot
+	// we have no choice to turn one into a telefrag meltdown
+	spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+
+	return spot;
+}
+
+edict_t *SelectDeathmatchSpawnPoint (void)
+{
+	if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
+		return SelectFarthestDeathmatchSpawnPoint ();
+	else
+		return SelectRandomDeathmatchSpawnPoint ();
+}
+
+//===============
+//ROGUE
+edict_t *SelectLavaCoopSpawnPoint (edict_t *)
+{
+	int		index;
+	edict_t	*spot;
+	float	lavatop;
+	edict_t	*lava;
+	edict_t *pointWithLeastLava;
+	float	lowest;
+	edict_t *spawnPoints [64];
+	vec3_t	center;
+	int		numPoints;
+	edict_t *highestlava;
+
+	lavatop = -99999;
+	highestlava = NULL;
+
+	// first, find the highest lava
+	// remember that some will stop moving when they've filled their
+	// areas...
+	lava = NULL;
+	while (1)
+	{
+		lava = G_Find (lava, FOFS(classname), "func_door");
+		if(!lava)
+			break;
+		
+		VectorAdd (lava->absmax, lava->absmin, center);
+		VectorScale (center, 0.5, center);
+
+		if(lava->spawnflags & 2 && (gi.pointcontents(center) & MASK_WATER))
+		{
+			if (lava->absmax[2] > lavatop)
+			{
+				lavatop = lava->absmax[2];
+				highestlava = lava;
+			}
+		}
+	}
+
+	// if we didn't find ANY lava, then return NULL
+	if (!highestlava)
+		return NULL;
+
+	// find the top of the lava and include a small margin of error (plus bbox size)
+	lavatop = highestlava->absmax[2] + 64;
+
+	// find all the lava spawn points and store them in spawnPoints[]
+	spot = NULL;
+	numPoints = 0;
+	while(spot = G_Find (spot, FOFS(classname), "info_player_coop_lava"))
+	{
+		if(numPoints == 64)
+			break;
+
+		spawnPoints[numPoints++] = spot;
+	}
+
+	if(numPoints < 1)
+		return NULL;
+
+	// walk up the sorted list and return the lowest, open, non-lava spawn point
+	lowest = 999999;
+	pointWithLeastLava = NULL;
+	for (index = 0; index < numPoints; index++)
+	{
+		if(spawnPoints[index]->s.origin[2] < lavatop)
+			continue;
+
+		if(PlayersRangeFromSpot(spawnPoints[index]) > 32)
+		{
+			if(spawnPoints[index]->s.origin[2] < lowest)
+			{
+				// save the last point
+				pointWithLeastLava = spawnPoints[index];
+				lowest = spawnPoints[index]->s.origin[2];
+			}
+		}
+	}
+
+	// FIXME - better solution????
+	// well, we may telefrag someone, but oh well...
+	if(pointWithLeastLava)
+		return pointWithLeastLava;
+
+	return NULL;
+}
+//ROGUE
+//===============
+
+edict_t *SelectCoopSpawnPoint (edict_t *ent)
+{
+	int		index;
+	edict_t	*spot;
+	char	*target;
+
+//ROGUE
+	// rogue hack, but not too gross...
+	if (!cistrcmp(level.mapname, "rmine2p") || !cistrcmp(level.mapname, "rmine2"))
+		return SelectLavaCoopSpawnPoint (ent);
+//ROGUE
+
+	index = ent->client - game.clients;
+
+	// player 0 starts in normal player spawn point
+	if (!index)
+		return NULL;
+
+	spot = NULL;
+
+	// assume there are four coop spots at each spawnpoint
+	while (1)
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_coop");
+		if (!spot)
+			return NULL;	// we didn't have enough...
+
+		target = spot->targetname;
+		if (!target)
+			target = "";
+		if ( cistrcmp(game.spawnpoint, target) == 0 )
+		{	// this is a coop spawn point for one of the clients here
+			index--;
+			if (!index)
+				return spot;		// this is it
+		}
+	}
+}
+
+
+/*
+===========
+SelectSpawnPoint
+
+Chooses a player start, deathmatch start, coop start, etc
+============
+*/
+void	SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
+{
+	edict_t	*spot = NULL;
+
+	if (deathmatch->value)
+		spot = SelectDeathmatchSpawnPoint ();
+	else if (coop->value)
+		spot = SelectCoopSpawnPoint (ent);
+
+	// find a single player start spot
+	if (!spot)
+	{
+		while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL)
+		{
+			if (!game.spawnpoint[0] && !spot->targetname)
+				break;
+
+			if (!game.spawnpoint[0] || !spot->targetname)
+				continue;
+
+			if (cistrcmp(game.spawnpoint, spot->targetname) == 0)
+				break;
+		}
+
+		if (!spot)
+		{
+			if (!game.spawnpoint[0])
+			{	// there wasn't a spawnpoint without a target, so use any
+				spot = G_Find (spot, FOFS(classname), "info_player_start");
+			}
+			if (!spot)
+				gi.error ("Couldn't find spawn point %s\n", game.spawnpoint);
+		}
+	}
+
+	VectorCopy (spot->s.origin, origin);
+	origin[2] += 9;
+	VectorCopy (spot->s.angles, angles);
+}
+
+//======================================================================
+
+
+void InitBodyQue (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.body_que = 0;
+	for (i=0; i<BODY_QUEUE_SIZE ; i++)
+	{
+		ent = G_Spawn();
+		ent->classname = "bodyque";
+	}
+}
+
+void body_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int	n;
+
+	if (self->health < -40)
+	{
+		gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->s.origin[2] -= 48;
+		ThrowClientHead (self, damage);
+		self->takedamage = DAMAGE_NO;
+	}
+}
+
+void CopyToBodyQue (edict_t *ent)
+{
+	edict_t		*body;
+
+	// grab a body que and cycle to the next one
+	body = &g_edicts[(int)maxclients->value + level.body_que + 1];
+	level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+	// FIXME: send an effect on the removed body
+
+	gi.unlinkentity (ent);
+
+	gi.unlinkentity (body);
+	body->s = ent->s;
+	body->s.number = body - g_edicts;
+
+	body->svflags = ent->svflags;
+	VectorCopy (ent->mins, body->mins);
+	VectorCopy (ent->maxs, body->maxs);
+	VectorCopy (ent->absmin, body->absmin);
+	VectorCopy (ent->absmax, body->absmax);
+	VectorCopy (ent->size, body->size);
+	body->solid = ent->solid;
+	body->clipmask = ent->clipmask;
+	body->owner = ent->owner;
+	body->movetype = ent->movetype;
+
+	body->die = body_die;
+	body->takedamage = DAMAGE_YES;
+
+	gi.linkentity (body);
+}
+
+
+void respawn (edict_t *self)
+{
+	if (deathmatch->value || coop->value)
+	{
+		// spectators don't leave bodies
+		if (self->movetype != MOVETYPE_NOCLIP)
+			CopyToBodyQue (self);
+		self->svflags &= ~SVF_NOCLIENT;
+		PutClientInServer (self);
+
+		// add a teleportation effect
+		self->s.event = EV_PLAYER_TELEPORT;
+
+		// hold in place briefly
+		self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		self->client->ps.pmove.pm_time = 14;
+
+		self->client->respawn_time = level.time;
+
+		return;
+	}
+
+	// restart the entire server
+	gi.AddCommandString ("menu_loadgame\n");
+}
+
+/* 
+ * only called when pers.spectator changes
+ * note that resp.spectator should be the opposite of pers.spectator here
+ */
+void spectator_respawn (edict_t *ent)
+{
+	int i, numspec;
+
+	// if the user wants to become a spectator, make sure he doesn't
+	// exceed max_spectators
+
+	if (ent->client->pers.spectator)
+	{
+		char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator");
+		if (*spectator_password->string && 
+			strcmp(spectator_password->string, "none") && 
+			strcmp(spectator_password->string, value))
+		{
+			gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
+			ent->client->pers.spectator = false;
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 0\n");
+			gi.unicast(ent, true);
+			return;
+		}
+
+		// count spectators
+		for (i = 1, numspec = 0; i <= maxclients->value; i++)
+		{
+			if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
+				numspec++;
+		}
+
+		if (numspec >= maxspectators->value)
+		{
+			gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
+			ent->client->pers.spectator = false;
+			// reset his spectator var
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 0\n");
+			gi.unicast(ent, true);
+			return;
+		}
+	}
+	else
+	{
+		// he was a spectator and wants to join the game
+		// he must have the right password
+		char *value = Info_ValueForKey (ent->client->pers.userinfo, "password");
+		if (*password->string && strcmp(password->string, "none") && 
+			strcmp(password->string, value))
+		{
+			gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
+			ent->client->pers.spectator = true;
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 1\n");
+			gi.unicast(ent, true);
+			return;
+		}
+	}
+
+	// clear score on respawn
+	ent->client->pers.score = ent->client->resp.score = 0;
+
+	ent->svflags &= ~SVF_NOCLIENT;
+	PutClientInServer (ent);
+
+	// add a teleportation effect
+	if (!ent->client->pers.spectator)
+	{
+		// send effect
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_LOGIN);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		// hold in place briefly
+		ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		ent->client->ps.pmove.pm_time = 14;
+	}
+
+	ent->client->respawn_time = level.time;
+
+	if (ent->client->pers.spectator) 
+		gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
+	else
+		gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
+}
+
+//==============================================================
+
+
+/*
+===========
+PutClientInServer
+
+Called when a player connects to a server or respawns in
+a deathmatch.
+============
+*/
+void PutClientInServer (edict_t *ent)
+{
+	vec3_t	mins = {-16, -16, -24};
+	vec3_t	maxs = {16, 16, 32};
+	int		index;
+	vec3_t	spawn_origin, spawn_angles;
+	gclient_t	*client;
+	int		i;
+	client_persistant_t	saved;
+	client_respawn_t	resp;
+
+	// find a spawn point
+	// do it before setting health back up, so farthest
+	// ranging doesn't count this client
+	if(gamerules && gamerules->value && DMGame.SelectSpawnPoint)		// PGM
+		DMGame.SelectSpawnPoint (ent, spawn_origin, spawn_angles);		// PGM
+	else																// PGM
+		SelectSpawnPoint (ent, spawn_origin, spawn_angles);
+
+	index = ent-g_edicts-1;
+	client = ent->client;
+
+	// deathmatch wipes most client data every spawn
+	if (deathmatch->value)
+	{
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		InitClientPersistant (client);
+		ClientUserinfoChanged (ent, userinfo);
+	}
+	else if (coop->value)
+	{
+//		int			n;
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		// this is kind of ugly, but it's how we want to handle keys in coop
+//		for (n = 0; n < game.num_items; n++)
+//		{
+//			if (itemlist[n].flags & IT_KEY)
+//				resp.coop_respawn.inventory[n] = client->pers.inventory[n];
+//		}
+		resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
+		resp.coop_respawn.helpchanged = client->pers.helpchanged;
+		client->pers = resp.coop_respawn;
+		ClientUserinfoChanged (ent, userinfo);
+		if (resp.score > client->pers.score)
+			client->pers.score = resp.score;
+	}
+	else
+	{
+		memset (&resp, 0, sizeof(resp));
+	}
+
+	// clear everything but the persistant data
+	saved = client->pers;
+	memset (client, 0, sizeof(*client));
+	client->pers = saved;
+	if (client->pers.health <= 0)
+		InitClientPersistant(client);
+	client->resp = resp;
+
+	// copy some data from the client to the entity
+	FetchClientEntData (ent);
+
+	// clear entity values
+	ent->groundentity = NULL;
+	ent->client = &game.clients[index];
+	ent->takedamage = DAMAGE_AIM;
+	ent->movetype = MOVETYPE_WALK;
+	ent->viewheight = 22;
+	ent->inuse = true;
+	ent->classname = "player";
+	ent->mass = 200;
+	ent->solid = SOLID_BBOX;
+	ent->deadflag = DEAD_NO;
+	ent->air_finished = level.time + 12;
+	ent->clipmask = MASK_PLAYERSOLID;
+	ent->model = "players/male/tris.md2";
+	ent->pain = player_pain;
+	ent->die = player_die;
+	ent->waterlevel = 0;
+	ent->watertype = 0;
+	ent->flags &= ~FL_NO_KNOCKBACK;
+	ent->svflags &= ~SVF_DEADMONSTER;
+
+	ent->flags &= ~FL_SAM_RAIMI;		// PGM - turn off sam raimi flag
+
+	VectorCopy (mins, ent->mins);
+	VectorCopy (maxs, ent->maxs);
+	VectorClear (ent->velocity);
+
+	// clear playerstate values
+	memset (&ent->client->ps, 0, sizeof(client->ps));
+
+	client->ps.pmove.origin[0] = spawn_origin[0]*8;
+	client->ps.pmove.origin[1] = spawn_origin[1]*8;
+	client->ps.pmove.origin[2] = spawn_origin[2]*8;
+
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		client->ps.fov = 90;
+	}
+	else
+	{
+		client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+		if (client->ps.fov < 1)
+			client->ps.fov = 90;
+		else if (client->ps.fov > 160)
+			client->ps.fov = 160;
+	}
+
+//PGM
+	if (client->pers.weapon)
+		client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+	else 
+		client->ps.gunindex = 0;
+//PGM
+
+	// clear entity state values
+	ent->s.effects = 0;
+	ent->s.modelindex = 255;		// will use the skin specified model
+	ent->s.modelindex2 = 255;		// custom gun model
+	// sknum is player num and weapon number
+	// weapon number will be added in changeweapon
+	ent->s.skinnum = ent - g_edicts - 1;
+
+	ent->s.frame = 0;
+	VectorCopy (spawn_origin, ent->s.origin);
+	ent->s.origin[2] += 1;	// make sure off ground
+	VectorCopy (ent->s.origin, ent->s.old_origin);
+
+	// set the delta angle
+	for (i=0 ; i<3 ; i++)
+		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
+
+	ent->s.angles[PITCH] = 0;
+	ent->s.angles[YAW] = spawn_angles[YAW];
+	ent->s.angles[ROLL] = 0;
+	VectorCopy (ent->s.angles, client->ps.viewangles);
+	VectorCopy (ent->s.angles, client->v_angle);
+
+	// spawn a spectator
+	if (client->pers.spectator)
+	{
+		client->chase_target = NULL;
+
+		client->resp.spectator = true;
+
+		ent->movetype = MOVETYPE_NOCLIP;
+		ent->solid = SOLID_NOT;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->client->ps.gunindex = 0;
+		gi.linkentity (ent);
+		return;
+	}
+	else
+		client->resp.spectator = false;
+
+	if (!KillBox (ent))
+	{	// could't spawn in?
+	}
+
+	gi.linkentity (ent);
+
+	// my tribute to cash's level-specific hacks. I hope I live
+	// up to his trailblazing cheese.
+	if(cistrcmp(level.mapname, "rboss") == 0)
+	{
+		// if you get on to rboss in single player or coop, ensure
+		// the player has the nuke key. (not in DM)
+		if(!(deathmatch->value))
+		{
+			gitem_t		*item;
+
+			item = FindItem("Antimatter Bomb");
+			client->pers.selected_item = ITEM_INDEX(item);
+			client->pers.inventory[client->pers.selected_item] = 1;
+		}
+	}
+
+	// force the current weapon up
+	client->newweapon = client->pers.weapon;
+	ChangeWeapon (ent);
+}
+
+/*
+=====================
+ClientBeginDeathmatch
+
+A client has just connected to the server in 
+deathmatch mode, so clear everything out before starting them.
+=====================
+*/
+void ClientBeginDeathmatch (edict_t *ent)
+{
+	G_InitEdict (ent);
+
+	InitClientResp (ent->client);
+
+	//PGM
+	if(gamerules && gamerules->value && DMGame.ClientBegin)	
+	{
+		DMGame.ClientBegin (ent);
+	}
+	//PGM
+
+		// locate ent at a spawn point
+	PutClientInServer (ent);
+
+	if (level.intermissiontime)
+	{
+		MoveClientToIntermission (ent);
+	}
+	else
+	{
+		// send effect
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_LOGIN);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+	}
+
+	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+
+/*
+===========
+ClientBegin
+
+called when a client has finished connecting, and is ready
+to be placed into the game.  This will happen every level load.
+============
+*/
+void ClientBegin (edict_t *ent)
+{
+	int		i;
+
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	if (deathmatch->value)
+	{
+		ClientBeginDeathmatch (ent);
+		return;
+	}
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == true)
+	{
+		// the client has cleared the client side viewangles upon
+		// connecting to the server, which is different than the
+		// state when the game is saved, so we need to compensate
+		// with deltaangles
+		for (i=0 ; i<3 ; i++)
+			ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
+	}
+	else
+	{
+		// a spawn point will completely reinitialize the entity
+		// except for the persistant data that was initialized at
+		// ClientConnect() time
+		G_InitEdict (ent);
+		ent->classname = "player";
+		InitClientResp (ent->client);
+		PutClientInServer (ent);
+	}
+
+	if (level.intermissiontime)
+	{
+		MoveClientToIntermission (ent);
+	}
+	else
+	{
+		// send effect if in a multiplayer game
+		if (game.maxclients > 1)
+		{
+			gi.WriteByte (svc_muzzleflash);
+			gi.WriteShort (ent-g_edicts);
+			gi.WriteByte (MZ_LOGIN);
+			gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+			gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+		}
+	}
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+/*
+===========
+ClientUserInfoChanged
+
+called whenever the player updates a userinfo variable.
+
+The game can override any of the settings in place
+(forcing skins or names, etc) before copying it off.
+============
+*/
+void ClientUserinfoChanged (edict_t *ent, char *userinfo)
+{
+	char	*s;
+	int		playernum;
+
+	// check for malformed or illegal info strings
+	if (!Info_Validate(userinfo))
+	{
+		strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt");
+	}
+
+	// set name
+	s = Info_ValueForKey (userinfo, "name");
+	strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
+
+	// set spectator
+	s = Info_ValueForKey (userinfo, "spectator");
+	// spectators are only supported in deathmatch
+	// if (deathmatch->value && strcmp(s, "0"))
+	if (deathmatch->value && *s && strcmp(s, "0"))
+		ent->client->pers.spectator = true;
+	else
+		ent->client->pers.spectator = false;
+
+	// set skin
+	s = Info_ValueForKey (userinfo, "skin");
+
+	playernum = ent-g_edicts-1;
+
+	// combine name and skin into a configstring
+	gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
+
+	// fov
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		ent->client->ps.fov = 90;
+	}
+	else
+	{
+		ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
+		if (ent->client->ps.fov < 1)
+			ent->client->ps.fov = 90;
+		else if (ent->client->ps.fov > 160)
+			ent->client->ps.fov = 160;
+	}
+
+	// handedness
+	s = Info_ValueForKey (userinfo, "hand");
+	if (strlen(s))
+	{
+		ent->client->pers.hand = atoi(s);
+	}
+
+	// save off the userinfo in case we want to check something later
+	strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
+}
+
+
+/*
+===========
+ClientConnect
+
+Called when a player begins connecting to the server.
+The game can refuse entrance to a client by returning false.
+If the client is allowed, the connection process will continue
+and eventually get to ClientBegin()
+Changing levels will NOT cause this to be called again, but
+loadgames will.
+============
+*/
+qboolean ClientConnect (edict_t *ent, char *userinfo)
+{
+	char	*value;
+
+	// check to see if they are on the banned IP list
+	value = Info_ValueForKey (userinfo, "ip");
+	if (SV_FilterPacket(value))
+	{
+		Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
+		return false;
+	}
+
+	// check for a spectator
+	value = Info_ValueForKey (userinfo, "spectator");
+//	if (deathmatch->value && strcmp(value, "0"))
+	if (deathmatch->value && *value && strcmp(value, "0"))
+	{
+		int i, numspec;
+
+		if (*spectator_password->string && 
+			strcmp(spectator_password->string, "none") && 
+			strcmp(spectator_password->string, value))
+		{
+			Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
+			return false;
+		}
+
+		// count spectators
+		for (i = numspec = 0; i < maxclients->value; i++)
+		{
+			if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator)
+				numspec++;
+		}
+
+		if (numspec >= maxspectators->value)
+		{
+			Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
+			return false;
+		}
+	}
+	else
+	{
+		// check for a password
+		value = Info_ValueForKey (userinfo, "password");
+		if (*password->string && strcmp(password->string, "none") && 
+			strcmp(password->string, value))
+		{
+			Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
+			return false;
+		}
+	}
+
+
+	// they can connect
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == false)
+	{
+		// clear the respawning variables
+		InitClientResp (ent->client);
+		if (!game.autosaved || !ent->client->pers.weapon)
+			InitClientPersistant (ent->client);
+	}
+
+	ClientUserinfoChanged (ent, userinfo);
+
+	if (game.maxclients > 1)
+		gi.dprintf ("%s connected\n", ent->client->pers.netname);
+
+	ent->svflags = 0; // make sure we start with known default
+	ent->client->pers.connected = true;
+	return true;
+}
+
+/*
+===========
+ClientDisconnect
+
+Called when a player drops from the server.
+Will not be called between levels.
+============
+*/
+void ClientDisconnect (edict_t *ent)
+{
+	int		playernum;
+
+	if (!ent->client)
+		return;
+
+	gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+//============
+//ROGUE
+	// make sure no trackers are still hurting us.
+	if(ent->client->tracker_pain_framenum)
+		RemoveAttackingPainDaemons (ent);
+
+	if (ent->client->owned_sphere)
+	{
+		if(ent->client->owned_sphere->inuse)
+			G_FreeEdict (ent->client->owned_sphere);
+		ent->client->owned_sphere = NULL;
+	}
+
+	if (gamerules && gamerules->value)
+	{
+		if(DMGame.PlayerDisconnect)
+			DMGame.PlayerDisconnect(ent);
+	}
+//ROGUE
+//============
+
+	// send effect
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_LOGOUT);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	gi.unlinkentity (ent);
+	ent->s.modelindex = 0;
+	ent->solid = SOLID_NOT;
+	ent->inuse = false;
+	ent->classname = "disconnected";
+	ent->client->pers.connected = false;
+
+	playernum = ent-g_edicts-1;
+	gi.configstring (CS_PLAYERSKINS+playernum, "");
+}
+
+
+//==============================================================
+
+
+edict_t	*pm_passent;
+
+// pmove doesn't need to know about passent and contentmask
+trace_t	PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
+{
+	if (pm_passent->health > 0)
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
+	else
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
+}
+
+unsigned CheckBlock (void *b, int c)
+{
+	int	v,i;
+	v = 0;
+	for (i=0 ; i<c ; i++)
+		v+= ((byte *)b)[i];
+	return v;
+}
+void PrintPmove (pmove_t *pm)
+{
+	unsigned	c1, c2;
+
+	c1 = CheckBlock (&pm->s, sizeof(pm->s));
+	c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd));
+	Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
+}
+
+/*
+==============
+ClientThink
+
+This will be called once for each client frame, which will
+usually be a couple times for each server frame.
+==============
+*/
+void ClientThink (edict_t *ent, usercmd_t *ucmd)
+{
+	gclient_t	*client;
+	edict_t	*other;
+	int		i, j;
+	pmove_t	pm;
+
+	level.current_entity = ent;
+	client = ent->client;
+
+	if (level.intermissiontime)
+	{
+		client->ps.pmove.pm_type = PM_FREEZE;
+		// can exit intermission after five seconds
+		if (level.time > level.intermissiontime + 5.0 
+			&& (ucmd->buttons & BUTTON_ANY) )
+			level.exitintermission = true;
+		return;
+	}
+
+	pm_passent = ent;
+
+	if (ent->client->chase_target)
+	{
+		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+	}
+	else
+	{
+		// set up for pmove
+		memset (&pm, 0, sizeof(pm));
+
+		if (ent->movetype == MOVETYPE_NOCLIP)
+			client->ps.pmove.pm_type = PM_SPECTATOR;
+		else if (ent->s.modelindex != 255)
+			client->ps.pmove.pm_type = PM_GIB;
+		else if (ent->deadflag)
+			client->ps.pmove.pm_type = PM_DEAD;
+		else
+			client->ps.pmove.pm_type = PM_NORMAL;
+
+	//PGM	trigger_gravity support
+	//	client->ps.pmove.gravity = sv_gravity->value;
+		client->ps.pmove.gravity = sv_gravity->value * ent->gravity;
+	//PGM
+		pm.s = client->ps.pmove;
+
+		for (i=0 ; i<3 ; i++)
+		{
+			pm.s.origin[i] = ent->s.origin[i]*8;
+			pm.s.velocity[i] = ent->velocity[i]*8;
+		}
+
+		if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+		{
+			pm.snapinitial = true;
+	//		gi.dprintf ("pmove changed!\n");
+		}
+
+		pm.cmd = *ucmd;
+
+		pm.trace = PM_trace;	// adds default parms
+		pm.pointcontents = gi.pointcontents;
+
+		// perform a pmove
+		gi.Pmove (&pm);
+
+		// save results of pmove
+		client->ps.pmove = pm.s;
+		client->old_pmove = pm.s;
+
+		for (i=0 ; i<3 ; i++)
+		{
+			ent->s.origin[i] = pm.s.origin[i]*0.125;
+			ent->velocity[i] = pm.s.velocity[i]*0.125;
+		}
+
+		VectorCopy (pm.mins, ent->mins);
+		VectorCopy (pm.maxs, ent->maxs);
+
+		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+		if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+			PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+		}
+
+	//ROGUE sam raimi cam support
+		if(ent->flags & FL_SAM_RAIMI)
+			ent->viewheight = 8;
+		else
+			ent->viewheight = pm.viewheight;
+	//ROGUE
+
+		ent->waterlevel = pm.waterlevel;
+		ent->watertype = pm.watertype;
+		ent->groundentity = pm.groundentity;
+		if (pm.groundentity)
+			ent->groundentity_linkcount = pm.groundentity->linkcount;
+
+		if (ent->deadflag)
+		{
+			client->ps.viewangles[ROLL] = 40;
+			client->ps.viewangles[PITCH] = -15;
+			client->ps.viewangles[YAW] = client->killer_yaw;
+		}
+		else
+		{
+			VectorCopy (pm.viewangles, client->v_angle);
+			VectorCopy (pm.viewangles, client->ps.viewangles);
+		}
+
+		gi.linkentity (ent);
+
+	//PGM trigger_gravity support
+		ent->gravity = 1.0;
+	//PGM
+		if (ent->movetype != MOVETYPE_NOCLIP)
+			G_TouchTriggers (ent);
+
+		// touch other objects
+		for (i=0 ; i<pm.numtouch ; i++)
+		{
+			other = pm.touchents[i];
+			for (j=0 ; j<i ; j++)
+				if (pm.touchents[j] == other)
+					break;
+			if (j != i)
+				continue;	// duplicated
+			if (!other->touch)
+				continue;
+			other->touch (other, ent, NULL, NULL);
+		}
+	}
+
+	client->oldbuttons = client->buttons;
+	client->buttons = ucmd->buttons;
+	client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+	// save light level the player is standing on for
+	// monster sighting AI
+	ent->light_level = ucmd->lightlevel;
+
+	// fire weapon from final position if needed
+	if (client->latched_buttons & BUTTON_ATTACK)
+	{
+		if (client->resp.spectator)
+		{
+			client->latched_buttons = 0;
+
+			if (client->chase_target)
+			{
+				client->chase_target = NULL;
+				client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+			}
+			else
+				GetChaseTarget(ent);
+		}
+		else if (!client->weapon_thunk)
+		{
+			client->weapon_thunk = true;
+			Think_Weapon (ent);
+		}
+	}
+
+	if (client->resp.spectator)
+	{
+		if (ucmd->upmove >= 10)
+		{
+			if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD))
+			{
+				client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
+				if (client->chase_target)
+					ChaseNext(ent);
+				else
+					GetChaseTarget(ent);
+			}
+		}
+		else
+			client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
+	}
+
+	// update chase cam if being followed
+	for (i = 1; i <= maxclients->value; i++)
+	{
+		other = g_edicts + i;
+		if (other->inuse && other->client->chase_target == ent)
+			UpdateChaseCam(other);
+	}
+}
+
+
+/*
+==============
+ClientBeginServerFrame
+
+This will be called once for each server frame, before running
+any other entities in the world.
+==============
+*/
+void ClientBeginServerFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	int			buttonMask;
+
+	if (level.intermissiontime)
+		return;
+
+	client = ent->client;
+
+	if (deathmatch->value &&
+		client->pers.spectator != client->resp.spectator &&
+		(level.time - client->respawn_time) >= 5)
+	{
+		spectator_respawn(ent);
+		return;
+	}
+
+	// run weapon animations if it hasn't been done by a ucmd_t
+	if (!client->weapon_thunk && !client->resp.spectator)
+		Think_Weapon (ent);
+	else
+		client->weapon_thunk = false;
+
+	if (ent->deadflag)
+	{
+		// wait for any button just going down
+		if ( level.time > client->respawn_time)
+		{
+			// in deathmatch, only wait for attack button
+			if (deathmatch->value)
+				buttonMask = BUTTON_ATTACK;
+			else
+				buttonMask = -1;
+
+			if ( ( client->latched_buttons & buttonMask ) ||
+				(deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) )
+			{
+				respawn(ent);
+				client->latched_buttons = 0;
+			}
+		}
+		return;
+	}
+
+	// add player trail so monsters can follow
+	if (!deathmatch->value)
+		if (!visible (ent, PlayerTrail_LastSpot() ) )
+			PlayerTrail_Add (ent->s.old_origin);
+
+	client->latched_buttons = 0;
+}
+
+/*
+==============
+RemoveAttackingPainDaemons
+
+This is called to clean up the pain daemons that the disruptor attaches
+to clients to damage them.
+==============
+*/
+void RemoveAttackingPainDaemons (edict_t *self)
+{
+	edict_t *tracker;
+
+	tracker = G_Find (NULL, FOFS(classname), "pain daemon");
+	while(tracker)
+	{
+		if(tracker->enemy == self)
+			G_FreeEdict(tracker);
+		tracker = G_Find (tracker, FOFS(classname), "pain daemon");
+	}
+
+	if(self->client)
+		self->client->tracker_pain_framenum = 0;
+}
--- /dev/null
+++ b/rogue/p_hud.c
@@ -1,0 +1,601 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+
+/*
+======================================================================
+
+INTERMISSION
+
+======================================================================
+*/
+
+void MoveClientToIntermission (edict_t *ent)
+{
+	if (deathmatch->value || coop->value)
+		ent->client->showscores = true;
+	VectorCopy (level.intermission_origin, ent->s.origin);
+	ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
+	ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
+	ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
+	VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
+	ent->client->ps.pmove.pm_type = PM_FREEZE;
+	ent->client->ps.gunindex = 0;
+	ent->client->ps.blend[3] = 0;
+	ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	// clean up powerup info
+	ent->client->quad_framenum = 0;
+	ent->client->invincible_framenum = 0;
+	ent->client->breather_framenum = 0;
+	ent->client->enviro_framenum = 0;
+	ent->client->grenade_blew_up = false;
+	ent->client->grenade_time = 0;
+
+	ent->client->ps.rdflags &= ~RDF_IRGOGGLES;		// PGM
+	ent->client->ir_framenum = 0;					// PGM
+	ent->client->nuke_framenum = 0;					// PMM
+	ent->client->double_framenum = 0;				// PMM
+
+	ent->viewheight = 0;
+	ent->s.modelindex = 0;
+	ent->s.modelindex2 = 0;
+	ent->s.modelindex3 = 0;
+	ent->s.modelindex = 0;
+	ent->s.effects = 0;
+	ent->s.sound = 0;
+	ent->solid = SOLID_NOT;
+
+	// add the layout
+
+	if (deathmatch->value || coop->value)
+	{
+		DeathmatchScoreboardMessage (ent, NULL);
+		gi.unicast (ent, true);
+	}
+
+}
+
+void BeginIntermission (edict_t *targ)
+{
+	int		i, n;
+	edict_t	*ent, *client;
+
+	if (level.intermissiontime)
+		return;		// already activated
+
+	game.autosaved = false;
+
+	// respawn any dead clients
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		if (client->health <= 0)
+			respawn(client);
+	}
+
+	level.intermissiontime = level.time;
+	level.changemap = targ->map;
+
+	if (strstr(level.changemap, "*"))
+	{
+		if (coop->value)
+		{
+			for (i=0 ; i<maxclients->value ; i++)
+			{
+				client = g_edicts + 1 + i;
+				if (!client->inuse)
+					continue;
+				// strip players of all keys between units
+				for (n = 0; n < MAX_ITEMS; n++)
+				{
+					if (itemlist[n].flags & IT_KEY)
+						client->client->pers.inventory[n] = 0;
+				}
+			}
+		}
+	}
+	else
+	{
+		if (!deathmatch->value)
+		{
+			level.exitintermission = 1;		// go immediately to the next level
+			return;
+		}
+	}
+
+	level.exitintermission = 0;
+
+	// find an intermission spot
+	ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
+	if (!ent)
+	{	// the map creator forgot to put in an intermission point...
+		ent = G_Find (NULL, FOFS(classname), "info_player_start");
+		if (!ent)
+			ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+	}
+	else
+	{	// chose one of four spots
+		i = rand() & 3;
+		while (i--)
+		{
+			ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+			if (!ent)	// wrap around the list
+				ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+		}
+	}
+
+	VectorCopy (ent->s.origin, level.intermission_origin);
+	VectorCopy (ent->s.angles, level.intermission_angle);
+
+	// move all clients to the intermission point
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		MoveClientToIntermission (client);
+	}
+}
+
+
+/*
+==================
+DeathmatchScoreboardMessage
+
+==================
+*/
+void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
+{
+	char	entry[1024];
+	char	string[1400];
+	int		stringlength;
+	int		i, j, k;
+	int		sorted[MAX_CLIENTS];
+	int		sortedscores[MAX_CLIENTS];
+	int		score, total;
+	int		x, y;
+	gclient_t	*cl;
+	edict_t		*cl_ent;
+	char	*tag;
+
+	// sort the clients by score
+	total = 0;
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		cl_ent = g_edicts + 1 + i;
+		if (!cl_ent->inuse || game.clients[i].resp.spectator)
+			continue;
+		score = game.clients[i].resp.score;
+		for (j=0 ; j<total ; j++)
+		{
+			if (score > sortedscores[j])
+				break;
+		}
+		for (k=total ; k>j ; k--)
+		{
+			sorted[k] = sorted[k-1];
+			sortedscores[k] = sortedscores[k-1];
+		}
+		sorted[j] = i;
+		sortedscores[j] = score;
+		total++;
+	}
+
+	// print level name and exit rules
+	string[0] = 0;
+
+	stringlength = strlen(string);
+
+	// add the clients in sorted order
+	if (total > 12)
+		total = 12;
+
+	for (i=0 ; i<total ; i++)
+	{
+		cl = &game.clients[sorted[i]];
+		cl_ent = g_edicts + 1 + sorted[i];
+
+		x = (i>=6) ? 160 : 0;
+		y = 32 + 32 * (i%6);
+
+		// add a dogtag
+		if (cl_ent == ent)
+			tag = "tag1";
+		else if (cl_ent == killer)
+			tag = "tag2";
+		else
+			tag = NULL;
+
+//===============
+//ROGUE
+		// allow new DM games to override the tag picture
+		if (gamerules && gamerules->value)
+		{
+			if(DMGame.DogTag)
+				DMGame.DogTag(cl_ent, killer, &tag);
+		}
+//ROGUE
+//===============
+
+		if (tag)
+		{
+			Com_sprintf (entry, sizeof(entry),
+				"xv %i yv %i picn %s ",x+32, y, tag);
+			j = strlen(entry);
+			if (stringlength + j > 1024)
+				break;
+			strcpy (string + stringlength, entry);
+			stringlength += j;
+		}
+
+		// send the layout
+		Com_sprintf (entry, sizeof(entry),
+			"client %i %i %i %i %i %i ",
+			x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
+		j = strlen(entry);
+		if (stringlength + j > 1024)
+			break;
+		strcpy (string + stringlength, entry);
+		stringlength += j;
+	}
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+}
+
+
+/*
+==================
+DeathmatchScoreboard
+
+Draw instead of help message.
+Note that it isn't that hard to overflow the 1400 byte message limit!
+==================
+*/
+void DeathmatchScoreboard (edict_t *ent)
+{
+	DeathmatchScoreboardMessage (ent, ent->enemy);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Score_f
+
+Display the scoreboard
+==================
+*/
+void Cmd_Score_f (edict_t *ent)
+{
+	ent->client->showinventory = false;
+	ent->client->showhelp = false;
+
+	if (!deathmatch->value && !coop->value)
+		return;
+
+	if (ent->client->showscores)
+	{
+		ent->client->showscores = false;
+		return;
+	}
+
+	ent->client->showscores = true;
+	DeathmatchScoreboard (ent);
+}
+
+
+/*
+==================
+HelpComputer
+
+Draw help computer.
+==================
+*/
+void HelpComputer (edict_t *ent)
+{
+	char	string[1024];
+	char	*sk;
+
+	if (skill->value == 0)
+		sk = "easy";
+	else if (skill->value == 1)
+		sk = "medium";
+	else if (skill->value == 2)
+		sk = "hard";
+	else
+		sk = "hard+";
+
+	// send the layout
+	Com_sprintf (string, sizeof(string),
+		"xv 32 yv 8 picn help "			// background
+		"xv 202 yv 12 string2 \"%s\" "		// skill
+		"xv 0 yv 24 cstring2 \"%s\" "		// level name
+		"xv 0 yv 54 cstring2 \"%s\" "		// help 1
+		"xv 0 yv 110 cstring2 \"%s\" "		// help 2
+		"xv 50 yv 164 string2 \" kills     goals    secrets\" "
+		"xv 50 yv 172 string2 \"%3i/%3i     %i/%i       %i/%i\" ", 
+		sk,
+		level.level_name,
+		game.helpmessage1,
+		game.helpmessage2,
+		level.killed_monsters, level.total_monsters, 
+		level.found_goals, level.total_goals,
+		level.found_secrets, level.total_secrets);
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Help_f
+
+Display the current help message
+==================
+*/
+void Cmd_Help_f (edict_t *ent)
+{
+	// this is for backwards compatability
+	if (deathmatch->value)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+
+	ent->client->showinventory = false;
+	ent->client->showscores = false;
+
+	if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged))
+	{
+		ent->client->showhelp = false;
+		return;
+	}
+
+	ent->client->showhelp = true;
+	ent->client->pers.helpchanged = 0;
+	HelpComputer (ent);
+}
+
+
+//=======================================================================
+
+/*
+===============
+G_SetStats
+===============
+*/
+void G_SetStats (edict_t *ent)
+{
+	gitem_t		*item;
+	int			index, cells = 0;
+	int			power_armor_type;
+
+	//
+	// health
+	//
+	ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
+	ent->client->ps.stats[STAT_HEALTH] = ent->health;
+
+	//
+	// ammo
+	//
+	if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
+	{
+		ent->client->ps.stats[STAT_AMMO_ICON] = 0;
+		ent->client->ps.stats[STAT_AMMO] = 0;
+	}
+	else
+	{
+		item = &itemlist[ent->client->ammo_index];
+		ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
+	}
+	
+	//
+	// armor
+	//
+	power_armor_type = PowerArmorType (ent);
+	if (power_armor_type)
+	{
+		cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
+		if (cells == 0)
+		{	// ran out of cells for power armor
+			ent->flags &= ~FL_POWER_ARMOR;
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+			power_armor_type = 0;;
+		}
+	}
+
+	index = ArmorIndex (ent);
+	if (power_armor_type && (!index || (level.framenum & 8) ) )
+	{	// flash between power armor and other armor icon
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
+		ent->client->ps.stats[STAT_ARMOR] = cells;
+	}
+	else if (index)
+	{
+		item = GetItemByIndex (index);
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
+	}
+	else
+	{
+		ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
+		ent->client->ps.stats[STAT_ARMOR] = 0;
+	}
+
+	//
+	// pickup message
+	//
+	if (level.time > ent->client->pickup_msg_time)
+	{
+		ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
+		ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
+	}
+
+	//
+	// timers
+	//
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
+	}
+// PMM
+	else if (ent->client->double_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_double");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->double_framenum - level.framenum)/10;
+	}
+// PMM
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
+	}
+// PGM
+	else if (ent->client->owned_sphere)
+	{
+		if(ent->client->owned_sphere->spawnflags == 1)			// defender
+			ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_defender");
+		else if(ent->client->owned_sphere->spawnflags == 2)		// hunter
+			ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_hunter");
+		else if(ent->client->owned_sphere->spawnflags == 4)		// vengeance
+			ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_vengeance");
+		else													// error case
+			ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("i_fixme");
+
+		ent->client->ps.stats[STAT_TIMER] = (int)(ent->client->owned_sphere->wait - level.time);
+	}
+	else if (ent->client->ir_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_ir");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->ir_framenum - level.framenum)/10;
+	}
+// PGM
+	else
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = 0;
+		ent->client->ps.stats[STAT_TIMER] = 0;
+	}
+
+	//
+	// selected item
+	//
+	if (ent->client->pers.selected_item == -1)
+		ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
+	else
+		ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
+
+	ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
+
+	//
+	// layouts
+	//
+	ent->client->ps.stats[STAT_LAYOUTS] = 0;
+
+	if (deathmatch->value)
+	{
+		if (ent->client->pers.health <= 0 || level.intermissiontime
+			|| ent->client->showscores)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+	else
+	{
+		if (ent->client->showscores || ent->client->showhelp)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+
+	//
+	// frags
+	//
+	ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
+
+	//
+	// help icon / current weapon if not shown
+	//
+	if (ent->client->pers.helpchanged && (level.framenum&8) )
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
+	else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
+		&& ent->client->pers.weapon)
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
+	else
+		ent->client->ps.stats[STAT_HELPICON] = 0;
+
+	ent->client->ps.stats[STAT_SPECTATOR] = 0;
+}
+
+/*
+===============
+G_CheckChaseStats
+===============
+*/
+void G_CheckChaseStats (edict_t *ent)
+{
+	int i;
+	gclient_t *cl;
+
+	for (i = 1; i <= maxclients->value; i++)
+	{
+		cl = g_edicts[i].client;
+		if (!g_edicts[i].inuse || cl->chase_target != ent)
+			continue;
+		memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
+		G_SetSpectatorStats(g_edicts + i);
+	}
+}
+
+/*
+===============
+G_SetSpectatorStats
+===============
+*/
+void G_SetSpectatorStats (edict_t *ent)
+{
+	gclient_t *cl = ent->client;
+
+	if (!cl->chase_target)
+		G_SetStats (ent);
+
+	cl->ps.stats[STAT_SPECTATOR] = 1;
+
+	// layouts are independant in spectator
+	cl->ps.stats[STAT_LAYOUTS] = 0;
+	if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
+		cl->ps.stats[STAT_LAYOUTS] |= 1;
+	if (cl->showinventory && cl->pers.health > 0)
+		cl->ps.stats[STAT_LAYOUTS] |= 2;
+
+	if (cl->chase_target && cl->chase_target->inuse)
+	{
+		cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + 
+			(cl->chase_target - g_edicts) - 1;
+	}
+	else
+		cl->ps.stats[STAT_CHASE] = 0;
+}
+
--- /dev/null
+++ b/rogue/p_trail.c
@@ -1,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+/*
+==============================================================================
+
+PLAYER TRAIL
+
+==============================================================================
+
+This is a circular list containing the a list of points of where
+the player has been recently.  It is used by monsters for pursuit.
+
+.origin		the spot
+.owner		forward link
+.aiment		backward link
+*/
+
+
+#define	TRAIL_LENGTH	8
+
+edict_t		*trail[TRAIL_LENGTH];
+int			trail_head;
+qboolean	trail_active = false;
+
+#define NEXT(n)		(((n) + 1) & (TRAIL_LENGTH - 1))
+#define PREV(n)		(((n) - 1) & (TRAIL_LENGTH - 1))
+
+
+void PlayerTrail_Init (void)
+{
+	int		n;
+
+	if (deathmatch->value /* FIXME || coop */)
+		return;
+
+	for (n = 0; n < TRAIL_LENGTH; n++)
+	{
+		trail[n] = G_Spawn();
+		trail[n]->classname = "player_trail";
+	}
+
+	trail_head = 0;
+	trail_active = true;
+}
+
+
+void PlayerTrail_Add (vec3_t spot)
+{
+	vec3_t	temp;
+
+	if (!trail_active)
+		return;
+
+	VectorCopy (spot, trail[trail_head]->s.origin);
+
+	trail[trail_head]->timestamp = level.time;
+
+	VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
+	trail[trail_head]->s.angles[1] = vectoyaw (temp);
+
+	trail_head = NEXT(trail_head);
+}
+
+
+void PlayerTrail_New (vec3_t spot)
+{
+	if (!trail_active)
+		return;
+
+	PlayerTrail_Init ();
+	PlayerTrail_Add (spot);
+}
+
+
+edict_t *PlayerTrail_PickFirst (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	if (visible(self, trail[marker]))
+	{
+		return trail[marker];
+	}
+
+	if (visible(self, trail[PREV(marker)]))
+	{
+		return trail[PREV(marker)];
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_PickNext (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_LastSpot (void)
+{
+	return trail[PREV(trail_head)];
+}
--- /dev/null
+++ b/rogue/p_view.c
@@ -1,0 +1,1179 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+
+static	edict_t		*current_player;
+static	gclient_t	*current_client;
+
+static	vec3_t	forward, right, up;
+float	xyspeed;
+
+float	bobmove;
+int		bobcycle;		// odd cycles are right foot going forward
+float	bobfracsin;		// sin(bobfrac*M_PI)
+
+/*
+===============
+SV_CalcRoll
+
+===============
+*/
+float SV_CalcRoll (vec3_t, vec3_t velocity)
+{
+	float	sign;
+	float	side;
+	float	value;
+	
+	side = DotProduct (velocity, right);
+	sign = side < 0 ? -1 : 1;
+	side = fabs(side);
+	
+	value = sv_rollangle->value;
+
+	if (side < sv_rollspeed->value)
+		side = side * value / sv_rollspeed->value;
+	else
+		side = value;
+	
+	return side*sign;
+	
+}
+
+
+/*
+===============
+P_DamageFeedback
+
+Handles color blends and view kicks
+===============
+*/
+void P_DamageFeedback (edict_t *player)
+{
+	gclient_t	*client;
+	float	side;
+	float	realcount, count, kick;
+	vec3_t	v;
+	int		r, l;
+	static	vec3_t	power_color = {0.0, 1.0, 0.0};
+	static	vec3_t	acolor = {1.0, 1.0, 1.0};
+	static	vec3_t	bcolor = {1.0, 0.0, 0.0};
+
+	client = player->client;
+
+	// flash the backgrounds behind the status numbers
+	client->ps.stats[STAT_FLASHES] = 0;
+	if (client->damage_blood)
+		client->ps.stats[STAT_FLASHES] |= 1;
+	if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+		client->ps.stats[STAT_FLASHES] |= 2;
+
+	// total points of damage shot at the player this frame
+	count = (client->damage_blood + client->damage_armor + client->damage_parmor);
+	if (count == 0)
+		return;		// didn't take any damage
+
+	// start a pain animation if still in the player model
+	if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255)
+	{
+		static int		i;
+
+		client->anim_priority = ANIM_PAIN;
+		if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		{
+			player->s.frame = FRAME_crpain1-1;
+			client->anim_end = FRAME_crpain4;
+		}
+		else
+		{
+			i = (i+1)%3;
+			switch (i)
+			{
+			case 0:
+				player->s.frame = FRAME_pain101-1;
+				client->anim_end = FRAME_pain104;
+				break;
+			case 1:
+				player->s.frame = FRAME_pain201-1;
+				client->anim_end = FRAME_pain204;
+				break;
+			case 2:
+				player->s.frame = FRAME_pain301-1;
+				client->anim_end = FRAME_pain304;
+				break;
+			}
+		}
+	}
+
+	realcount = count;
+	if (count < 10)
+		count = 10;	// always make a visible effect
+
+	// play an apropriate pain sound
+	if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+	{
+		r = 1 + (rand()&1);
+		player->pain_debounce_time = level.time + 0.7;
+		if (player->health < 25)
+			l = 25;
+		else if (player->health < 50)
+			l = 50;
+		else if (player->health < 75)
+			l = 75;
+		else
+			l = 100;
+		gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
+	}
+
+	// the total alpha of the blend is always proportional to count
+	if (client->damage_alpha < 0)
+		client->damage_alpha = 0;
+	client->damage_alpha += count*0.01;
+	if (client->damage_alpha < 0.2)
+		client->damage_alpha = 0.2;
+	if (client->damage_alpha > 0.6)
+		client->damage_alpha = 0.6;		// don't go too saturated
+
+	// the color of the blend will vary based on how much was absorbed
+	// by different armors
+	VectorClear (v);
+	if (client->damage_parmor)
+		VectorMA (v, (float)client->damage_parmor/realcount, power_color, v);
+	if (client->damage_armor)
+		VectorMA (v, (float)client->damage_armor/realcount,  acolor, v);
+	if (client->damage_blood)
+		VectorMA (v, (float)client->damage_blood/realcount,  bcolor, v);
+	VectorCopy (v, client->damage_blend);
+
+
+	//
+	// calculate view angle kicks
+	//
+	kick = abs(client->damage_knockback);
+	if (kick && player->health > 0)	// kick of 0 means no view adjust at all
+	{
+		kick = kick * 100 / player->health;
+
+		if (kick < count*0.5)
+			kick = count*0.5;
+		if (kick > 50)
+			kick = 50;
+
+		VectorSubtract (client->damage_from, player->s.origin, v);
+		VectorNormalize (v);
+		
+		side = DotProduct (v, right);
+		client->v_dmg_roll = kick*side*0.3;
+		
+		side = -DotProduct (v, forward);
+		client->v_dmg_pitch = kick*side*0.3;
+
+		client->v_dmg_time = level.time + DAMAGE_TIME;
+	}
+
+	//
+	// clear totals
+	//
+	client->damage_blood = 0;
+	client->damage_armor = 0;
+	client->damage_parmor = 0;
+	client->damage_knockback = 0;
+}
+
+
+
+
+/*
+===============
+SV_CalcViewOffset
+
+Auto pitching on slopes?
+
+  fall from 128: 400 = 160000
+  fall from 256: 580 = 336400
+  fall from 384: 720 = 518400
+  fall from 512: 800 = 640000
+  fall from 640: 960 = 
+
+  damage = deltavelocity*deltavelocity  * 0.0001
+
+===============
+*/
+void SV_CalcViewOffset (edict_t *ent)
+{
+	float		*angles;
+	float		bob;
+	float		ratio;
+	float		delta;
+	vec3_t		v;
+
+
+//===================================
+
+	// base angles
+	angles = ent->client->ps.kick_angles;
+
+	// if dead, fix the angle and don't add any kick
+	if (ent->deadflag)
+	{
+		VectorClear (angles);
+
+		if(ent->flags & FL_SAM_RAIMI)
+		{
+			ent->client->ps.viewangles[ROLL] = 0;
+			ent->client->ps.viewangles[PITCH] = 0;
+		}
+		else
+		{
+			ent->client->ps.viewangles[ROLL] = 40;
+			ent->client->ps.viewangles[PITCH] = -15;
+		}
+		ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
+	}
+	else
+	{
+		// add angles based on weapon kick
+
+		VectorCopy (ent->client->kick_angles, angles);
+
+		// add angles based on damage kick
+
+		ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
+		if (ratio < 0)
+		{
+			ratio = 0;
+			ent->client->v_dmg_pitch = 0;
+			ent->client->v_dmg_roll = 0;
+		}
+		angles[PITCH] += ratio * ent->client->v_dmg_pitch;
+		angles[ROLL] += ratio * ent->client->v_dmg_roll;
+
+		// add pitch based on fall kick
+
+		ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+		if (ratio < 0)
+			ratio = 0;
+		angles[PITCH] += ratio * ent->client->fall_value;
+
+		// add angles based on velocity
+
+		delta = DotProduct (ent->velocity, forward);
+		angles[PITCH] += delta*run_pitch->value;
+		
+		delta = DotProduct (ent->velocity, right);
+		angles[ROLL] += delta*run_roll->value;
+
+		// add angles based on bob
+
+		delta = bobfracsin * bob_pitch->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		angles[PITCH] += delta;
+		delta = bobfracsin * bob_roll->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		if (bobcycle & 1)
+			delta = -delta;
+		angles[ROLL] += delta;
+	}
+
+//===================================
+
+	// base origin
+
+	VectorClear (v);
+
+	// add view height
+
+	v[2] += ent->viewheight;
+
+	// add fall height
+
+	ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+	if (ratio < 0)
+		ratio = 0;
+	v[2] -= ratio * ent->client->fall_value * 0.4;
+
+	// add bob height
+
+	bob = bobfracsin * xyspeed * bob_up->value;
+	if (bob > 6)
+		bob = 6;
+	//gi.DebugGraph (bob *2, 255);
+	v[2] += bob;
+
+	// add kick offset
+
+	VectorAdd (v, ent->client->kick_origin, v);
+
+	// absolutely bound offsets
+	// so the view can never be outside the player box
+
+	if (v[0] < -14)
+		v[0] = -14;
+	else if (v[0] > 14)
+		v[0] = 14;
+	if (v[1] < -14)
+		v[1] = -14;
+	else if (v[1] > 14)
+		v[1] = 14;
+	if (v[2] < -22)
+		v[2] = -22;
+	else if (v[2] > 30)
+		v[2] = 30;
+
+	VectorCopy (v, ent->client->ps.viewoffset);
+}
+
+/*
+==============
+SV_CalcGunOffset
+==============
+*/
+void SV_CalcGunOffset (edict_t *ent)
+{
+	int		i;
+	float	delta;
+	//ROGUE
+	static gitem_t	*heatbeam;
+
+	if (!heatbeam)
+		heatbeam = FindItemByClassname ("weapon_plasmabeam");
+
+	//ROGUE - heatbeam shouldn't bob so the beam looks right
+	if (ent->client->pers.weapon != heatbeam)
+	{
+	// ROGUE
+		// gun angles from bobbing
+		ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
+		ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
+		if (bobcycle & 1)
+		{
+			ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
+			ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
+		}
+
+		ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
+
+		// gun angles from delta movement
+		for (i=0 ; i<3 ; i++)
+		{
+			delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i];
+			if (delta > 180)
+				delta -= 360;
+			if (delta < -180)
+				delta += 360;
+			if (delta > 45)
+				delta = 45;
+			if (delta < -45)
+				delta = -45;
+			if (i == YAW)
+				ent->client->ps.gunangles[ROLL] += 0.1*delta;
+			ent->client->ps.gunangles[i] += 0.2 * delta;
+		}
+	}
+	// ROGUE
+	else
+	{
+		for (i=0; i<3; i++)
+			ent->client->ps.gunangles[i] = 0;
+	}
+	//ROGUE
+
+	// gun height
+	VectorClear (ent->client->ps.gunoffset);
+//	ent->ps->gunorigin[2] += bob;
+
+	// gun_x / gun_y / gun_z are development tools
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value);
+		ent->client->ps.gunoffset[i] += right[i]*gun_x->value;
+		ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value);
+	}
+}
+
+
+/*
+=============
+SV_AddBlend
+=============
+*/
+void SV_AddBlend (float r, float g, float b, float a, float *v_blend)
+{
+	float	a2, a3;
+
+	if (a <= 0)
+		return;
+	a2 = v_blend[3] + (1-v_blend[3])*a;	// new total alpha
+	a3 = v_blend[3]/a2;		// fraction of color from old
+
+	v_blend[0] = v_blend[0]*a3 + r*(1-a3);
+	v_blend[1] = v_blend[1]*a3 + g*(1-a3);
+	v_blend[2] = v_blend[2]*a3 + b*(1-a3);
+	v_blend[3] = a2;
+}
+
+
+/*
+=============
+SV_CalcBlend
+=============
+*/
+void SV_CalcBlend (edict_t *ent)
+{
+	int		contents;
+	vec3_t	vieworg;
+	int		remaining;
+
+	ent->client->ps.blend[0] = ent->client->ps.blend[1] = 
+		ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
+
+	// add for contents
+	VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg);
+	contents = gi.pointcontents (vieworg);
+	if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) )
+		ent->client->ps.rdflags |= RDF_UNDERWATER;
+	else
+		ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	if (contents & (CONTENTS_SOLID|CONTENTS_LAVA))
+		SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_SLIME)
+		SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_WATER)
+		SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
+
+	// add for powerups
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend);
+	}
+	// PMM - double damage
+	else if (ent->client->double_framenum > level.framenum)
+	{
+		remaining = ent->client->double_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0.9, 0.7, 0, 0.08, ent->client->ps.blend);
+	}
+	// PMM
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		remaining = ent->client->enviro_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		remaining = ent->client->breather_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend);
+	}
+
+//PGM
+	if(ent->client->nuke_framenum > level.framenum)
+	{
+		float brightness;
+		brightness = (ent->client->nuke_framenum - level.framenum) / 20.0;
+		SV_AddBlend (1, 1, 1, brightness, ent->client->ps.blend);
+	}
+	if (ent->client->ir_framenum > level.framenum)
+	{
+		remaining = ent->client->ir_framenum - level.framenum;
+		if(remaining > 30 || (remaining & 4))
+		{
+			ent->client->ps.rdflags |= RDF_IRGOGGLES;
+			SV_AddBlend (1, 0, 0, 0.2, ent->client->ps.blend);
+		}
+		else
+			ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
+	}
+	else
+	{
+		ent->client->ps.rdflags &= ~RDF_IRGOGGLES;
+	}
+//PGM
+
+	// add for damage
+	if (ent->client->damage_alpha > 0)
+		SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1]
+		,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend);
+
+	if (ent->client->bonus_alpha > 0)
+		SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend);
+
+	// drop the damage value
+	ent->client->damage_alpha -= 0.06;
+	if (ent->client->damage_alpha < 0)
+		ent->client->damage_alpha = 0;
+
+	// drop the bonus value
+	ent->client->bonus_alpha -= 0.1;
+	if (ent->client->bonus_alpha < 0)
+		ent->client->bonus_alpha = 0;
+}
+
+
+/*
+=================
+P_FallingDamage
+=================
+*/
+void P_FallingDamage (edict_t *ent)
+{
+	float	delta;
+	int		damage;
+	vec3_t	dir;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+		return;
+
+	if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity))
+	{
+		delta = ent->client->oldvelocity[2];
+	}
+	else
+	{
+		if (!ent->groundentity)
+			return;
+		delta = ent->velocity[2] - ent->client->oldvelocity[2];
+	}
+	delta = delta*delta * 0.0001;
+
+	// never take falling damage if completely underwater
+	if (ent->waterlevel == 3)
+		return;
+	if (ent->waterlevel == 2)
+		delta *= 0.25;
+	if (ent->waterlevel == 1)
+		delta *= 0.5;
+
+	if (delta < 1)
+		return;
+
+	if (delta < 15)
+	{
+		ent->s.event = EV_FOOTSTEP;
+		return;
+	}
+
+	ent->client->fall_value = delta*0.5;
+	if (ent->client->fall_value > 40)
+		ent->client->fall_value = 40;
+	ent->client->fall_time = level.time + FALL_TIME;
+
+	if (delta > 30)
+	{
+		if (ent->health > 0)
+		{
+			if (delta >= 55)
+				ent->s.event = EV_FALLFAR;
+			else
+				ent->s.event = EV_FALL;
+		}
+		ent->pain_debounce_time = level.time;	// no normal pain sound
+		damage = (delta-30)/2;
+		if (damage < 1)
+			damage = 1;
+		VectorSet (dir, 0, 0, 1);
+
+		if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) )
+			T_Damage (ent, WORLD, WORLD, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING);
+	}
+	else
+	{
+		ent->s.event = EV_FALLSHORT;
+		return;
+	}
+}
+
+
+
+/*
+=============
+P_WorldEffects
+=============
+*/
+void P_WorldEffects (void)
+{
+	qboolean	breather;
+	qboolean	envirosuit;
+	int			waterlevel, old_waterlevel;
+
+	if (current_player->movetype == MOVETYPE_NOCLIP)
+	{
+		current_player->air_finished = level.time + 12;	// don't need air
+		return;
+	}
+
+	waterlevel = current_player->waterlevel;
+	old_waterlevel = current_client->old_waterlevel;
+	current_client->old_waterlevel = waterlevel;
+
+	breather = current_client->breather_framenum > level.framenum;
+	envirosuit = current_client->enviro_framenum > level.framenum;
+
+	//
+	// if just entered a water volume, play a sound
+	//
+	if (!old_waterlevel && waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		if (current_player->watertype & CONTENTS_LAVA)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_SLIME)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_WATER)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		current_player->flags |= FL_INWATER;
+
+		// clear damage_debounce, so the pain sound will play immediately
+		current_player->damage_debounce_time = level.time - 1;
+	}
+
+	//
+	// if just completely exited a water volume, play a sound
+	//
+	if (old_waterlevel && ! waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+		current_player->flags &= ~FL_INWATER;
+	}
+
+	//
+	// check for head just going under water
+	//
+	if (old_waterlevel != 3 && waterlevel == 3)
+	{
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
+	}
+
+	//
+	// check for head just coming out of water
+	//
+	if (old_waterlevel == 3 && waterlevel != 3)
+	{
+		if (current_player->air_finished < level.time)
+		{	// gasp for air
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
+			PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		}
+		else  if (current_player->air_finished < level.time + 11)
+		{	// just break surface
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
+		}
+	}
+
+	//
+	// check for drowning
+	//
+	if (waterlevel == 3)
+	{
+		// breather or envirosuit give air
+		if (breather || envirosuit)
+		{
+			current_player->air_finished = level.time + 10;
+
+			if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
+			{
+				if (!current_client->breather_sound)
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
+				current_client->breather_sound ^= 1;
+				PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+				//FIXME: release a bubble?
+			}
+		}
+
+		// if out of air, start drowning
+		if (current_player->air_finished < level.time)
+		{	// drown!
+			if (current_player->client->next_drown_time < level.time 
+				&& current_player->health > 0)
+			{
+				current_player->client->next_drown_time = level.time + 1;
+
+				// take more damage the longer underwater
+				current_player->dmg += 2;
+				if (current_player->dmg > 15)
+					current_player->dmg = 15;
+
+				// play a gurp sound instead of a normal pain sound
+				if (current_player->health <= current_player->dmg)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
+				else if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
+
+				current_player->pain_debounce_time = level.time;
+
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+			}
+		}
+	}
+	else
+	{
+		current_player->air_finished = level.time + 12;
+		current_player->dmg = 2;
+	}
+
+	//
+	// check for sizzle damage
+	//
+	if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+	{
+		if (current_player->watertype & CONTENTS_LAVA)
+		{
+			if (current_player->health > 0
+				&& current_player->pain_debounce_time <= level.time
+				&& current_client->invincible_framenum < level.framenum)
+			{
+				if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
+				current_player->pain_debounce_time = level.time + 1;
+			}
+
+			if (envirosuit)	// take 1/3 damage with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA);
+			else
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA);
+		}
+
+		if (current_player->watertype & CONTENTS_SLIME)
+		{
+			if (!envirosuit)
+			{	// no damage from slime with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME);
+			}
+		}
+	}
+}
+
+
+/*
+===============
+G_SetClientEffects
+===============
+*/
+void G_SetClientEffects (edict_t *ent)
+{
+	int		pa_type;
+	int		remaining;
+
+	ent->s.effects = 0;
+//	ent->s.renderfx = 0;
+
+	// PGM - player is always ir visible, even dead.
+	ent->s.renderfx = RF_IR_VISIBLE;
+
+	if (ent->health <= 0 || level.intermissiontime)
+		return;
+
+//=========
+//PGM
+	if(ent->flags & FL_DISGUISED)
+		ent->s.renderfx |= RF_USE_DISGUISE;
+
+	if (gamerules && gamerules->value)
+	{
+		if(DMGame.PlayerEffects)
+			DMGame.PlayerEffects(ent);
+	}
+//PGM
+//=========
+
+	if (ent->powerarmor_time > level.time)
+	{
+		pa_type = PowerArmorType (ent);
+		if (pa_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (pa_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_QUAD;
+	}
+
+//=======
+//ROGUE
+	if (ent->client->double_framenum > level.framenum)
+	{
+		remaining = ent->client->double_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_DOUBLE;
+	}
+	if ((ent->client->owned_sphere) && (ent->client->owned_sphere->spawnflags == 1))
+	{
+		ent->s.effects |= EF_HALF_DAMAGE;
+	}
+	if (ent->client->tracker_pain_framenum > level.framenum)
+	{
+		ent->s.effects |= EF_TRACKERTRAIL;
+	}
+//ROGUE
+//=======
+
+	if (ent->client->invincible_framenum > level.framenum)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_PENT;
+	}
+
+	// show cheaters!!!
+	if (ent->flags & FL_GODMODE)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
+	}
+
+//PGM
+/*
+	if (ent->client->torch_framenum > level.framenum)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_FLASHLIGHT);
+		gi.WritePosition (ent->s.origin);
+		gi.WriteShort (ent - g_edicts);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+	}
+*/
+//PGM
+}
+
+
+/*
+===============
+G_SetClientEvent
+===============
+*/
+void G_SetClientEvent (edict_t *ent)
+{
+	if (ent->s.event)
+		return;
+
+	if ( ent->groundentity && xyspeed > 225)
+	{
+		if ( (int)(current_client->bobtime+bobmove) != bobcycle )
+			ent->s.event = EV_FOOTSTEP;
+	}
+}
+
+/*
+===============
+G_SetClientSound
+===============
+*/
+void G_SetClientSound (edict_t *ent)
+{
+	char	*weap;
+
+	if (ent->client->pers.game_helpchanged != game.helpchanged)
+	{
+		ent->client->pers.game_helpchanged = game.helpchanged;
+		ent->client->pers.helpchanged = 1;
+	}
+
+	// help beep (no more than three times)
+	if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && !(level.framenum&63) )
+	{
+		ent->client->pers.helpchanged++;
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
+	}
+
+
+	if (ent->client->pers.weapon)
+		weap = ent->client->pers.weapon->classname;
+	else
+		weap = "";
+
+	if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+		ent->s.sound = snd_fry;
+	else if (strcmp(weap, "weapon_railgun") == 0)
+		ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
+	else if (strcmp(weap, "weapon_bfg") == 0)
+		ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
+	else if (ent->client->weapon_sound)
+		ent->s.sound = ent->client->weapon_sound;
+	else
+		ent->s.sound = 0;
+}
+
+/*
+===============
+G_SetClientFrame
+===============
+*/
+void G_SetClientFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	qboolean	duck, run;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+	client = ent->client;
+
+	if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		duck = true;
+	else
+		duck = false;
+	if (xyspeed)
+		run = true;
+	else
+		run = false;
+
+	// check for stand/duck and stop/go transitions
+	if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH)
+		goto newanim;
+	if (run != client->anim_run && client->anim_priority == ANIM_BASIC)
+		goto newanim;
+	if (!ent->groundentity && client->anim_priority <= ANIM_WAVE)
+		goto newanim;
+
+	if(client->anim_priority == ANIM_REVERSE)
+	{
+		if(ent->s.frame > client->anim_end)
+		{
+			ent->s.frame--;
+			return;
+		}
+	}
+	else if (ent->s.frame < client->anim_end)
+	{	// continue an animation
+		ent->s.frame++;
+		return;
+	}
+
+	if (client->anim_priority == ANIM_DEATH)
+		return;		// stay there
+	if (client->anim_priority == ANIM_JUMP)
+	{
+		if (!ent->groundentity)
+			return;		// stay there
+		ent->client->anim_priority = ANIM_WAVE;
+		ent->s.frame = FRAME_jump3;
+		ent->client->anim_end = FRAME_jump6;
+		return;
+	}
+
+newanim:
+	// return to either a running or standing frame
+	client->anim_priority = ANIM_BASIC;
+	client->anim_duck = duck;
+	client->anim_run = run;
+
+	if (!ent->groundentity)
+	{
+		client->anim_priority = ANIM_JUMP;
+		if (ent->s.frame != FRAME_jump2)
+			ent->s.frame = FRAME_jump1;
+		client->anim_end = FRAME_jump2;
+	}
+	else if (run)
+	{	// running
+		if (duck)
+		{
+			ent->s.frame = FRAME_crwalk1;
+			client->anim_end = FRAME_crwalk6;
+		}
+		else
+		{
+			ent->s.frame = FRAME_run1;
+			client->anim_end = FRAME_run6;
+		}
+	}
+	else
+	{	// standing
+		if (duck)
+		{
+			ent->s.frame = FRAME_crstnd01;
+			client->anim_end = FRAME_crstnd19;
+		}
+		else
+		{
+			ent->s.frame = FRAME_stand01;
+			client->anim_end = FRAME_stand40;
+		}
+	}
+}
+
+
+/*
+=================
+ClientEndServerFrame
+
+Called for each player at the end of the server frame
+and right after spawning
+=================
+*/
+void ClientEndServerFrame (edict_t *ent)
+{
+	float	bobtime;
+	int		i;
+
+	current_player = ent;
+	current_client = ent->client;
+
+	//
+	// If the origin or velocity have changed since ClientThink(),
+	// update the pmove values.  This will happen when the client
+	// is pushed by a bmodel or kicked by an explosion.
+	// 
+	// If it wasn't updated here, the view position would lag a frame
+	// behind the body position when pushed -- "sinking into plats"
+	//
+	for (i=0 ; i<3 ; i++)
+	{
+		current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0;
+		current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0;
+	}
+
+	//
+	// If the end of unit layout is displayed, don't give
+	// the player any normal movement attributes
+	//
+	if (level.intermissiontime)
+	{
+		// FIXME: add view drifting here?
+		current_client->ps.blend[3] = 0;
+		current_client->ps.fov = 90;
+		G_SetStats (ent);
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+	// burn from lava, etc
+	P_WorldEffects ();
+
+	//
+	// set model angles from view angles so other things in
+	// the world can tell which direction you are looking
+	//
+	if (ent->client->v_angle[PITCH] > 180)
+		ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3;
+	else
+		ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3;
+	ent->s.angles[YAW] = ent->client->v_angle[YAW];
+	ent->s.angles[ROLL] = 0;
+	ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4;
+
+	//
+	// calculate speed and cycle to be used for
+	// all cyclic walking effects
+	//
+	xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);
+
+	if (xyspeed < 5)
+	{
+		bobmove = 0;
+		current_client->bobtime = 0;	// start at beginning of cycle again
+	}
+	else if (ent->groundentity)
+	{	// so bobbing only cycles when on ground
+		if (xyspeed > 210)
+			bobmove = 0.25;
+		else if (xyspeed > 100)
+			bobmove = 0.125;
+		else
+			bobmove = 0.0625;
+	}
+	
+	bobtime = (current_client->bobtime += bobmove);
+
+	if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
+		bobtime *= 4;
+
+	bobcycle = (int)bobtime;
+	bobfracsin = fabs(sin(bobtime*M_PI));
+
+	// detect hitting the floor
+	P_FallingDamage (ent);
+
+	// apply all the damage taken this frame
+	P_DamageFeedback (ent);
+
+	// determine the view offsets
+	SV_CalcViewOffset (ent);
+
+	// determine the gun offsets
+	SV_CalcGunOffset (ent);
+
+	// determine the full screen color blend
+	// must be after viewoffset, so eye contents can be
+	// accurately determined
+	// FIXME: with client prediction, the contents
+	// should be determined by the client
+	SV_CalcBlend (ent);
+
+	// chase cam stuff
+	if (ent->client->resp.spectator)
+		G_SetSpectatorStats(ent);
+	else
+		G_SetStats (ent);
+
+	G_CheckChaseStats(ent);
+
+	G_SetClientEvent (ent);
+
+	G_SetClientEffects (ent);
+
+	G_SetClientSound (ent);
+
+	G_SetClientFrame (ent);
+
+	VectorCopy (ent->velocity, ent->client->oldvelocity);
+	VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles);
+
+	// clear weapon kicks
+	VectorClear (ent->client->kick_origin);
+	VectorClear (ent->client->kick_angles);
+
+	// if the scoreboard is up, update it
+	if (ent->client->showscores && !(level.framenum & 31) )
+	{
+		DeathmatchScoreboardMessage (ent, ent->enemy);
+		gi.unicast (ent, false);
+	}
+}
+
--- /dev/null
+++ b/rogue/p_weapon.c
@@ -1,0 +1,2271 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+static qboolean	is_quad;
+static byte		is_silenced;
+
+//PGM
+static byte		damage_multiplier;
+//PGM
+
+void weapon_grenade_fire (edict_t *ent, qboolean held);
+
+//========
+//ROGUE
+byte P_DamageModifier(edict_t *ent)
+{
+	is_quad = 0;
+	damage_multiplier = 1;
+
+	if(ent->client->quad_framenum > level.framenum)
+	{
+		damage_multiplier *= 4;
+		is_quad = 1;
+
+		// if we're quad and DF_NO_STACK_DOUBLE is on, return now.
+		if(((int)(dmflags->value) & DF_NO_STACK_DOUBLE))
+			return damage_multiplier;
+	}
+	if(ent->client->double_framenum > level.framenum)
+	{
+		if ((deathmatch->value) || (damage_multiplier == 1))
+		{
+			damage_multiplier *= 2;
+			is_quad = 1;
+		}
+	}
+	
+	return damage_multiplier;
+}
+//ROGUE
+//========
+
+static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	vec3_t	_distance;
+
+	VectorCopy (distance, _distance);
+	if (client->pers.hand == LEFT_HANDED)
+		_distance[1] *= -1;
+	else if (client->pers.hand == CENTER_HANDED)
+		_distance[1] = 0;
+	G_ProjectSource (point, _distance, forward, right, result);
+}
+
+static void P_ProjectSource2 (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, 
+							  vec3_t right, vec3_t up, vec3_t result)
+{
+	vec3_t	_distance;
+
+	VectorCopy (distance, _distance);
+	if (client->pers.hand == LEFT_HANDED)
+		_distance[1] *= -1;
+	else if (client->pers.hand == CENTER_HANDED)
+		_distance[1] = 0;
+	G_ProjectSource2 (point, _distance, forward, right, up, result);
+}
+
+/*
+===============
+PlayerNoise
+
+Each player can have two noise objects associated with it:
+a personal noise (jumping, pain, weapon firing), and a weapon
+target noise (bullet wall impacts)
+
+Monsters that don't directly see the player can move
+to a noise in hopes of seeing the player from there.
+===============
+*/
+void PlayerNoise(edict_t *who, vec3_t where, int type)
+{
+	edict_t		*noise;
+
+	if (type == PNOISE_WEAPON)
+	{
+		if (who->client->silencer_shots)
+		{
+			who->client->silencer_shots--;
+			return;
+		}
+	}
+
+	if (deathmatch->value)
+		return;
+
+	if (who->flags & FL_NOTARGET)
+		return;
+
+	if (who->flags & FL_DISGUISED)
+	{
+		if (type == PNOISE_WEAPON)
+		{
+			level.disguise_violator = who;
+			level.disguise_violation_framenum = level.framenum + 5;
+		}
+		else
+			return;
+	}
+
+	if (!who->mynoise)
+	{
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise = noise;
+
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise2 = noise;
+	}
+
+	if (type == PNOISE_SELF || type == PNOISE_WEAPON)
+	{
+		noise = who->mynoise;
+		level.sound_entity = noise;
+		level.sound_entity_framenum = level.framenum;
+	}
+	else // type == PNOISE_IMPACT
+	{
+		noise = who->mynoise2;
+		level.sound2_entity = noise;
+		level.sound2_entity_framenum = level.framenum;
+	}
+
+	VectorCopy (where, noise->s.origin);
+	VectorSubtract (where, noise->maxs, noise->absmin);
+	VectorAdd (where, noise->maxs, noise->absmax);
+	noise->teleport_time = level.time;
+	gi.linkentity (noise);
+}
+
+
+qboolean Pickup_Weapon (edict_t *ent, edict_t *other)
+{
+	int			index;
+	gitem_t		*ammo;
+
+	index = ITEM_INDEX(ent->item);
+
+	if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) 
+		&& other->client->pers.inventory[index])
+	{
+		if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) )
+			return false;	// leave the weapon for others to pickup
+	}
+
+	other->client->pers.inventory[index]++;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) )
+	{
+		// give them some ammo with it
+		// PGM -- IF APPROPRIATE!
+		if(ent->item->ammo)			//PGM
+		{
+			ammo = FindItem (ent->item->ammo);
+			if ( (int)dmflags->value & DF_INFINITE_AMMO )
+				Add_Ammo (other, ammo, 1000);
+			else
+				Add_Ammo (other, ammo, ammo->quantity);
+		}
+
+		if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) )
+		{
+			if (deathmatch->value)
+			{
+				if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+					ent->flags |= FL_RESPAWN;
+				else
+					SetRespawn (ent, 30);
+			}
+			if (coop->value)
+				ent->flags |= FL_RESPAWN;
+		}
+	}
+
+	if (other->client->pers.weapon != ent->item && 
+		(other->client->pers.inventory[index] == 1) &&
+		( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) )
+		other->client->newweapon = ent->item;
+
+	return true;
+}
+
+
+/*
+===============
+ChangeWeapon
+
+The old weapon has been dropped all the way, so make the new one
+current
+===============
+*/
+void ChangeWeapon (edict_t *ent)
+{
+	int i;
+
+	if (ent->client->grenade_time)
+	{
+		ent->client->grenade_time = level.time;
+		ent->client->weapon_sound = 0;
+		weapon_grenade_fire (ent, false);
+		ent->client->grenade_time = 0;
+	}
+
+	ent->client->pers.lastweapon = ent->client->pers.weapon;
+	ent->client->pers.weapon = ent->client->newweapon;
+	ent->client->newweapon = NULL;
+	ent->client->machinegun_shots = 0;
+
+	// set visible model
+	if (ent->s.modelindex == 255)
+	{
+		if (ent->client->pers.weapon)
+			i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
+		else
+			i = 0;
+		ent->s.skinnum = (ent - g_edicts - 1) | i;
+	}
+
+	if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
+		ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
+	else
+		ent->client->ammo_index = 0;
+
+	if (!ent->client->pers.weapon)
+	{	// dead
+		ent->client->ps.gunindex = 0;
+		return;
+	}
+
+	ent->client->weaponstate = WEAPON_ACTIVATING;
+	ent->client->ps.gunframe = 0;
+	ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
+
+	ent->client->anim_priority = ANIM_PAIN;
+	if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+			ent->s.frame = FRAME_crpain1;
+			ent->client->anim_end = FRAME_crpain4;
+	}
+	else
+	{
+			ent->s.frame = FRAME_pain301;
+			ent->client->anim_end = FRAME_pain304;
+			
+	}
+}
+
+/*
+=================
+NoAmmoWeaponChange
+=================
+*/
+
+// PMM - added rogue weapons to the list
+
+void NoAmmoWeaponChange (edict_t *ent)
+{
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] )
+	{
+		ent->client->newweapon = FindItem ("railgun");
+		return;
+	}
+	// ROGUE
+	if ( (ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))] >= 2)
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("Plasma Beam"))] )
+	{
+		ent->client->newweapon = FindItem ("Plasma Beam");
+		return;
+	}
+	// -ROGUE
+	/*
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] )
+	{
+		ent->client->newweapon = FindItem ("hyperblaster");
+		return;
+	}
+	*/
+	// ROGUE
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("flechettes"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("etf rifle"))] )
+	{
+		ent->client->newweapon = FindItem ("etf rifle");
+		return;
+	}
+	// -ROGUE
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] )
+	{
+		ent->client->newweapon = FindItem ("chaingun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] )
+	{
+		ent->client->newweapon = FindItem ("machinegun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("super shotgun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("shotgun");
+		return;
+	}
+	ent->client->newweapon = FindItem ("blaster");
+}
+
+/*
+=================
+Think_Weapon
+
+Called by ClientBeginServerFrame and ClientThink
+=================
+*/
+void Think_Weapon (edict_t *ent)
+{
+	// if just died, put the weapon away
+	if (ent->health < 1)
+	{
+		ent->client->newweapon = NULL;
+		ChangeWeapon (ent);
+	}
+
+	// call active weapon think routine
+	if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
+	{
+//PGM
+		P_DamageModifier(ent);	
+//		is_quad = (ent->client->quad_framenum > level.framenum);
+//PGM
+		if (ent->client->silencer_shots)
+			is_silenced = MZ_SILENCED;
+		else
+			is_silenced = 0;
+		ent->client->pers.weapon->weaponthink (ent);
+	}
+}
+
+
+/*
+================
+Use_Weapon
+
+Make the weapon ready if there is ammo
+================
+*/
+void Use_Weapon (edict_t *ent, gitem_t *item)
+{
+	int			ammo_index;
+	gitem_t		*ammo_item;
+
+	// see if we're already using it
+	if (item == ent->client->pers.weapon)
+		return;
+
+	if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO))
+	{
+		ammo_item = FindItem(item->ammo);
+		ammo_index = ITEM_INDEX(ammo_item);
+
+		if (!ent->client->pers.inventory[ammo_index])
+		{
+			gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+			return;
+		}
+
+		if (ent->client->pers.inventory[ammo_index] < item->quantity)
+		{
+			gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+			return;
+		}
+	}
+
+	// change to this weapon when down
+	ent->client->newweapon = item;
+}
+
+
+
+/*
+================
+Drop_Weapon
+================
+*/
+void Drop_Weapon (edict_t *ent, gitem_t *item)
+{
+	int		index;
+
+	if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+		return;
+
+	index = ITEM_INDEX(item);
+	// see if we're already using it
+	if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) )
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n");
+		return;
+	}
+
+	Drop_Item (ent, item);
+	ent->client->pers.inventory[index]--;
+}
+
+
+/*
+================
+Weapon_Generic
+
+A generic function to handle the basics of weapon thinking
+================
+*/
+#define FRAME_FIRE_FIRST		(FRAME_ACTIVATE_LAST + 1)
+#define FRAME_IDLE_FIRST		(FRAME_FIRE_LAST + 1)
+#define FRAME_DEACTIVATE_FIRST	(FRAME_IDLE_LAST + 1)
+
+void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+{
+	int		n;
+
+	if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses
+	{
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_DROPPING)
+	{
+		if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
+		{
+			ChangeWeapon (ent);
+			return;
+		}
+		else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4)
+		{
+			ent->client->anim_priority = ANIM_REVERSE;
+			if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crpain4+1;
+				ent->client->anim_end = FRAME_crpain1;
+			}
+			else
+			{
+				ent->s.frame = FRAME_pain304+1;
+				ent->client->anim_end = FRAME_pain301;
+				
+			}
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
+		{
+			ent->client->weaponstate = WEAPON_READY;
+			ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+			return;
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
+	{
+		ent->client->weaponstate = WEAPON_DROPPING;
+		ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
+
+		if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4)
+		{
+			ent->client->anim_priority = ANIM_REVERSE;
+			if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crpain4+1;
+				ent->client->anim_end = FRAME_crpain1;
+			}
+			else
+			{
+				ent->s.frame = FRAME_pain304+1;
+				ent->client->anim_end = FRAME_pain301;
+				
+			}
+		}
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if ((!ent->client->ammo_index) || 
+				( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
+			{
+				ent->client->ps.gunframe = FRAME_FIRE_FIRST;
+				ent->client->weaponstate = WEAPON_FIRING;
+
+				// start the animation
+				ent->client->anim_priority = ANIM_ATTACK;
+				if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+				{
+					ent->s.frame = FRAME_crattak1-1;
+					ent->client->anim_end = FRAME_crattak9;
+				}
+				else
+				{
+					ent->s.frame = FRAME_attack1-1;
+					ent->client->anim_end = FRAME_attack8;
+				}
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+		}
+		else
+		{
+			if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+			{
+				ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+				return;
+			}
+
+			if (pause_frames)
+			{
+				for (n = 0; pause_frames[n]; n++)
+				{
+					if (ent->client->ps.gunframe == pause_frames[n])
+					{
+						if (rand()&15)
+							return;
+					}
+				}
+			}
+
+			ent->client->ps.gunframe++;
+			return;
+		}
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		for (n = 0; fire_frames[n]; n++)
+		{
+			if (ent->client->ps.gunframe == fire_frames[n])
+			{
+				// FIXME - double should use different sound
+				if (ent->client->quad_framenum > level.framenum)
+					gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+				else if (ent->client->double_framenum > level.framenum)
+					gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage3.wav"), 1, ATTN_NORM, 0);
+
+				fire (ent);
+				break;
+			}
+		}
+
+		if (!fire_frames[n])
+			ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
+			ent->client->weaponstate = WEAPON_READY;
+	}
+}
+
+
+/*
+======================================================================
+
+GRENADE
+
+======================================================================
+*/
+
+#define GRENADE_TIMER		3.0
+#define GRENADE_MINSPEED	400
+#define GRENADE_MAXSPEED	800
+
+void weapon_grenade_fire (edict_t *ent, qboolean held)
+{
+	vec3_t	offset;
+	vec3_t	forward, right, up;
+	vec3_t	start;
+	int		damage = 125;
+	float	timer;
+	int		speed;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+//		damage *= 4;
+		damage *= damage_multiplier;		// PGM
+
+	AngleVectors (ent->client->v_angle, forward, right, up);
+	if (ent->client->pers.weapon->tag == AMMO_TESLA)
+	{
+//		VectorSet(offset, 0, -12, ent->viewheight-26);
+		VectorSet(offset, 0, -4, ent->viewheight-22);
+	}
+	else
+	{
+//		VectorSet(offset, 8, 8, ent->viewheight-8);
+		VectorSet(offset, 2, 6, ent->viewheight-14);
+	}
+	P_ProjectSource2 (ent->client, ent->s.origin, offset, forward, right, up, start);
+
+	timer = ent->client->grenade_time - level.time;
+	speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+	if (speed > GRENADE_MAXSPEED)
+		speed = GRENADE_MAXSPEED;
+
+//	fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held);
+
+// ============
+// PGM
+	switch(ent->client->pers.weapon->tag)
+	{
+		case AMMO_GRENADES:
+			fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held);
+			break;
+		case AMMO_TESLA:
+			fire_tesla (ent, start, forward, damage_multiplier, speed);
+			break;
+		default:
+			fire_prox (ent, start, forward, damage_multiplier, speed);
+			break;
+	}
+// PGM
+// ============
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->grenade_time = level.time + 1.0;
+
+	if(ent->deadflag || ent->s.modelindex != 255) // VWep animations screw up corpses
+	{
+		return;
+	}
+
+	if (ent->health <= 0)
+		return;
+
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->client->anim_priority = ANIM_ATTACK;
+		ent->s.frame = FRAME_crattak1-1;
+		ent->client->anim_end = FRAME_crattak3;
+	}
+	else
+	{
+		ent->client->anim_priority = ANIM_REVERSE;
+		ent->s.frame = FRAME_wave08;
+		ent->client->anim_end = FRAME_wave01;
+	}
+}
+/*
+void Weapon_Grenade (edict_t *ent)
+{
+	if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+	{
+		ChangeWeapon (ent);
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		ent->client->weaponstate = WEAPON_READY;
+		ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if (ent->client->pers.inventory[ent->client->ammo_index])
+			{
+				ent->client->ps.gunframe = 1;
+				ent->client->weaponstate = WEAPON_FIRING;
+				ent->client->grenade_time = 0;
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+			return;
+		}
+
+		if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48))
+		{
+			if (rand()&15)
+				return;
+		}
+
+		if (++ent->client->ps.gunframe > 48)
+			ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->ps.gunframe == 5)
+			gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+
+		if (ent->client->ps.gunframe == 11)
+		{
+			if (!ent->client->grenade_time)
+			{
+				ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+				ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+			}
+
+			// they waited too long, detonate it in their hand
+			if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
+			{
+				ent->client->weapon_sound = 0;
+				weapon_grenade_fire (ent, true);
+				ent->client->grenade_blew_up = true;
+			}
+
+			if (ent->client->buttons & BUTTON_ATTACK)
+				return;
+
+			if (ent->client->grenade_blew_up)
+			{
+				if (level.time >= ent->client->grenade_time)
+				{
+					ent->client->ps.gunframe = 15;
+					ent->client->grenade_blew_up = false;
+				}
+				else
+				{
+					return;
+				}
+			}
+		}
+
+		if (ent->client->ps.gunframe == 12)
+		{
+			ent->client->weapon_sound = 0;
+			weapon_grenade_fire (ent, false);
+		}
+
+		if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time))
+			return;
+
+		ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == 16)
+		{
+			ent->client->grenade_time = 0;
+			ent->client->weaponstate = WEAPON_READY;
+		}
+	}
+}
+*/
+
+//void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+//									15                      48						5						11					12				29,34,39,48
+void Throw_Generic (edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_THROW_SOUND,
+					int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, int *pause_frames, int EXPLODE,
+					void (*fire)(edict_t *ent, qboolean held))
+{
+	int n;
+
+	if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+	{
+		ChangeWeapon (ent);
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		ent->client->weaponstate = WEAPON_READY;
+		ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if (ent->client->pers.inventory[ent->client->ammo_index])
+			{
+				ent->client->ps.gunframe = 1;
+				ent->client->weaponstate = WEAPON_FIRING;
+				ent->client->grenade_time = 0;
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+			return;
+		}
+
+		if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+		{
+			ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+			return;
+		}
+
+		if (pause_frames)
+		{
+			for (n = 0; pause_frames[n]; n++)
+			{
+				if (ent->client->ps.gunframe == pause_frames[n])
+				{
+					if (rand()&15)
+						return;
+				}
+			}
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->ps.gunframe == FRAME_THROW_SOUND)
+			gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+
+		if (ent->client->ps.gunframe == FRAME_THROW_HOLD)
+		{
+			if (!ent->client->grenade_time)
+			{
+				ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+				switch(ent->client->pers.weapon->tag)
+				{
+					case AMMO_GRENADES:
+						ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+						break;
+				}
+			}
+
+			// they waited too long, detonate it in their hand
+			if (EXPLODE && !ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
+			{
+				ent->client->weapon_sound = 0;
+				fire (ent, true);
+				ent->client->grenade_blew_up = true;
+			}
+
+			if (ent->client->buttons & BUTTON_ATTACK)
+				return;
+
+			if (ent->client->grenade_blew_up)
+			{
+				if (level.time >= ent->client->grenade_time)
+				{
+					ent->client->ps.gunframe = FRAME_FIRE_LAST;
+					ent->client->grenade_blew_up = false;
+				}
+				else
+				{
+					return;
+				}
+			}
+		}
+
+		if (ent->client->ps.gunframe == FRAME_THROW_FIRE)
+		{
+			ent->client->weapon_sound = 0;
+			fire (ent, true);
+		}
+
+		if ((ent->client->ps.gunframe == FRAME_FIRE_LAST) && (level.time < ent->client->grenade_time))
+			return;
+
+		ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == FRAME_IDLE_FIRST)
+		{
+			ent->client->grenade_time = 0;
+			ent->client->weaponstate = WEAPON_READY;
+		}
+	}
+}
+
+//void Throw_Generic (edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_THROW_SOUND,
+//						int FRAME_THROW_HOLD, int FRAME_THROW_FIRE, int *pause_frames, 
+//						int EXPLOSION_TIME, void (*fire)(edict_t *ent))
+
+void Weapon_Grenade (edict_t *ent)
+{
+	static int	pause_frames[]	= {29,34,39,48,0};
+
+	Throw_Generic (ent, 15, 48, 5, 11, 12, pause_frames, GRENADE_TIMER, weapon_grenade_fire);
+}
+
+void Weapon_Prox (edict_t *ent)
+{
+	static int	pause_frames[]	= {22, 29, 0};
+
+	Throw_Generic (ent, 7, 27, 99, 2, 4, pause_frames, 0, weapon_grenade_fire);
+}
+
+void Weapon_Tesla (edict_t *ent)
+{
+	static int	pause_frames[]	= {21, 0};
+
+	if ((ent->client->ps.gunframe > 1) && (ent->client->ps.gunframe < 9))
+	{
+		ent->client->ps.gunindex = gi.modelindex  ("models/weapons/v_tesla2/tris.md2");
+	}
+	else
+	{
+		ent->client->ps.gunindex = gi.modelindex  ("models/weapons/v_tesla/tris.md2");
+	}
+
+	Throw_Generic (ent, 8, 32, 99, 1, 2, pause_frames, 0, weapon_grenade_fire);
+}
+
+
+
+/*
+======================================================================
+
+GRENADE LAUNCHER
+
+======================================================================
+*/
+
+void weapon_grenadelauncher_fire (edict_t *ent)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+//	int		damage = 120;
+	int		damage;			// PGM
+	float	radius;
+
+// =====
+// PGM
+	switch(ent->client->pers.weapon->tag)
+	{
+		case AMMO_PROX:
+			damage = 90;
+			break;
+		default:
+			damage = 120;
+			break;
+	}
+// PGM
+// =====
+
+	radius = damage+40;
+	if (is_quad)
+//		damage *= 4;
+		damage *= damage_multiplier;		//pgm
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+//	fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
+// =====
+// PGM
+	switch(ent->client->pers.weapon->tag)
+	{
+		case AMMO_PROX:
+			fire_prox (ent, start, forward, damage_multiplier, 600);
+			break;
+		default:
+			fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
+			break;
+	}
+// PGM
+// =====
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_GRENADE | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_GrenadeLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {34, 51, 59, 0};
+	static int	fire_frames[]	= {6, 0};
+
+	Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
+}
+
+//==========
+//PGM
+void Weapon_ProxLauncher (edict_t *ent)
+{
+	static int      pause_frames[]  = {34, 51, 59, 0};
+	static int      fire_frames[]   = {6, 0};
+
+	Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
+}
+//PGM
+//==========
+
+/*
+======================================================================
+
+ROCKET
+
+======================================================================
+*/
+
+void Weapon_RocketLauncher_Fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius;
+	int		radius_damage;
+
+	damage = 100 + (int)(qrandom() * 20.0);
+	radius_damage = 120;
+	damage_radius = 120;
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		radius_damage *= 4;
+		radius_damage *= damage_multiplier;
+//PGM
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_ROCKET | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_RocketLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {25, 33, 42, 50, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
+}
+
+
+/*
+======================================================================
+
+BLASTER / HYPERBLASTER
+
+======================================================================
+*/
+
+void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	offset;
+
+	if (is_quad)
+//		damage *= 4;		
+		damage *= damage_multiplier;		//pgm
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	VectorSet(offset, 24, 8, ent->viewheight-8);
+	VectorAdd (offset, g_offset, offset);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	fire_blaster (ent, start, forward, damage, 1000, effect, hyper);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	if (hyper)
+		gi.WriteByte (MZ_HYPERBLASTER | is_silenced);
+	else
+		gi.WriteByte (MZ_BLASTER | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+
+void Weapon_Blaster_Fire (edict_t *ent)
+{
+	int		damage;
+
+	if (deathmatch->value)
+		damage = 15;
+	else
+		damage = 10;
+	Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
+	ent->client->ps.gunframe++;
+}
+
+void Weapon_Blaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {19, 32, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
+}
+
+
+void Weapon_HyperBlaster_Fire (edict_t *ent)
+{
+	float	rotation;
+	vec3_t	offset;
+	int		effect;
+	int		damage;
+
+	ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe++;
+	}
+	else
+	{
+		if (! ent->client->pers.inventory[ent->client->ammo_index] )
+		{
+			if (level.time >= ent->pain_debounce_time)
+			{
+				gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+				ent->pain_debounce_time = level.time + 1;
+			}
+			NoAmmoWeaponChange (ent);
+		}
+		else
+		{
+			rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6;
+			offset[0] = -4 * sin(rotation);
+			offset[1] = 0;
+			offset[2] = 4 * cos(rotation);
+
+			if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9))
+				effect = EF_HYPERBLASTER;
+			else
+				effect = 0;
+			if (deathmatch->value)
+				damage = 15;
+			else
+				damage = 20;
+			Blaster_Fire (ent, offset, damage, true, effect);
+			if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+				ent->client->pers.inventory[ent->client->ammo_index]--;
+
+			ent->client->anim_priority = ANIM_ATTACK;
+			if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crattak1 - 1;
+				ent->client->anim_end = FRAME_crattak9;
+			}
+			else
+			{
+				ent->s.frame = FRAME_attack1 - 1;
+				ent->client->anim_end = FRAME_attack8;
+			}
+		}
+
+		ent->client->ps.gunframe++;
+		if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index])
+			ent->client->ps.gunframe = 6;
+	}
+
+	if (ent->client->ps.gunframe == 12)
+	{
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+		ent->client->weapon_sound = 0;
+	}
+
+}
+
+void Weapon_HyperBlaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {0};
+	static int	fire_frames[]	= {6, 7, 8, 9, 10, 11, 0};
+
+	Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
+}
+
+/*
+======================================================================
+
+MACHINEGUN / CHAINGUN
+
+======================================================================
+*/
+
+void Machinegun_Fire (edict_t *ent)
+{
+	int	i;
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		angles;
+	int			damage = 8;
+	int			kick = 2;
+	vec3_t		offset;
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->machinegun_shots = 0;
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->ps.gunframe == 5)
+		ent->client->ps.gunframe = 4;
+	else
+		ent->client->ps.gunframe = 5;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
+	{
+		ent->client->ps.gunframe = 6;
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		kick *= 4;
+		kick *= damage_multiplier;
+//PGM
+	}
+
+	for (i=1 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+	ent->client->kick_origin[0] = crandom() * 0.35;
+	ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;
+
+	// raise the gun as it is firing
+	if (!deathmatch->value)
+	{
+		ent->client->machinegun_shots++;
+		if (ent->client->machinegun_shots > 9)
+			ent->client->machinegun_shots = 9;
+	}
+
+	// get start / end positions
+	VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
+	AngleVectors (angles, forward, right, NULL);
+	VectorSet(offset, 0, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_MACHINEGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - (int) (qrandom()+0.25);
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - (int) (qrandom()+0.25);
+		ent->client->anim_end = FRAME_attack8;
+	}
+}
+
+void Weapon_Machinegun (edict_t *ent)
+{
+	static int	pause_frames[]	= {23, 45, 0};
+	static int	fire_frames[]	= {4, 5, 0};
+
+	Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire);
+}
+
+void Chaingun_Fire (edict_t *ent)
+{
+	int			i;
+	int			shots;
+	vec3_t		start;
+	vec3_t		forward, right, up;
+	float		r, u;
+	vec3_t		offset;
+	int			damage;
+	int			kick = 2;
+
+	if (deathmatch->value)
+		damage = 6;
+	else
+		damage = 8;
+
+	if (ent->client->ps.gunframe == 5)
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0);
+
+	if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe = 32;
+		ent->client->weapon_sound = 0;
+		return;
+	}
+	else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK)
+		&& ent->client->pers.inventory[ent->client->ammo_index])
+	{
+		ent->client->ps.gunframe = 15;
+	}
+	else
+	{
+		ent->client->ps.gunframe++;
+	}
+
+	if (ent->client->ps.gunframe == 22)
+	{
+		ent->client->weapon_sound = 0;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0);
+	}
+	else
+	{
+		ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
+	}
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1);
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1);
+		ent->client->anim_end = FRAME_attack8;
+	}
+
+	if (ent->client->ps.gunframe <= 9)
+		shots = 1;
+	else if (ent->client->ps.gunframe <= 14)
+	{
+		if (ent->client->buttons & BUTTON_ATTACK)
+			shots = 2;
+		else
+			shots = 1;
+	}
+	else
+		shots = 3;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < shots)
+		shots = ent->client->pers.inventory[ent->client->ammo_index];
+
+	if (!shots)
+	{
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		kick *= 4;
+		kick *= damage_multiplier;
+//PGM
+	}
+
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+
+	for (i=0 ; i<shots ; i++)
+	{
+		// get start / end positions
+		AngleVectors (ent->client->v_angle, forward, right, up);
+		r = 7 + crandom()*4;
+		u = crandom()*4;
+		VectorSet(offset, 0, r, u + ent->viewheight-8);
+		P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+		fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
+	}
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= shots;
+}
+
+
+void Weapon_Chaingun (edict_t *ent)
+{
+	static int	pause_frames[]	= {38, 43, 51, 61, 0};
+	static int	fire_frames[]	= {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+
+	Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire);
+}
+
+
+/*
+======================================================================
+
+SHOTGUN / SUPERSHOTGUN
+
+======================================================================
+*/
+
+void weapon_shotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage = 4;
+	int			kick = 8;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		kick *= 4;
+		kick *= damage_multiplier;
+//PGM
+	}
+
+	if (deathmatch->value)
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
+	else
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_Shotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {22, 28, 34, 0};
+	static int	fire_frames[]	= {8, 9, 0};
+
+	Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire);
+}
+
+
+void weapon_supershotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	vec3_t		v;
+	int			damage = 6;
+	int			kick = 12;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		kick *= 4;
+		kick *= damage_multiplier;
+//PGM
+	}
+
+	v[PITCH] = ent->client->v_angle[PITCH];
+	v[YAW]   = ent->client->v_angle[YAW] - 5;
+	v[ROLL]  = ent->client->v_angle[ROLL];
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+	v[YAW]   = ent->client->v_angle[YAW] + 5;
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SSHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 2;
+}
+
+void Weapon_SuperShotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {29, 42, 57, 0};
+	static int	fire_frames[]	= {7, 0};
+
+	Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
+}
+
+
+
+/*
+======================================================================
+
+RAILGUN
+
+======================================================================
+*/
+
+void weapon_railgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage;
+	int			kick;
+
+	if (deathmatch->value)
+	{	// normal damage is too extreme in dm
+		damage = 100;
+		kick = 200;
+	}
+	else
+	{
+		damage = 150;
+		kick = 250;
+	}
+
+	if (is_quad)
+	{
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//		kick *= 4;
+		kick *= damage_multiplier;
+//PGM
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -3, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -3;
+
+	VectorSet(offset, 0, 7,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rail (ent, start, forward, damage, kick);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_RAILGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+
+void Weapon_Railgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {56, 0};
+	static int	fire_frames[]	= {4, 0};
+
+	Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire);
+}
+
+
+/*
+======================================================================
+
+BFG10K
+
+======================================================================
+*/
+
+void weapon_bfg_fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius = 1000;
+
+	if (deathmatch->value)
+		damage = 200;
+	else
+		damage = 500;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		// send muzzle flash
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_BFG | is_silenced);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		ent->client->ps.gunframe++;
+
+		PlayerNoise(ent, start, PNOISE_WEAPON);
+		return;
+	}
+
+	// cells can go down during windup (from power armor hits), so
+	// check again and abort firing if we don't have enough now
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 50)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (is_quad)
+//PGM
+//		damage *= 4;
+		damage *= damage_multiplier;
+//PGM
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+
+	// make a big pitch kick with an inverse fall
+	ent->client->v_dmg_pitch = -40;
+	ent->client->v_dmg_roll = crandom()*8;
+	ent->client->v_dmg_time = level.time + DAMAGE_TIME;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bfg (ent, start, forward, damage, 400, damage_radius);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 50;
+}
+
+void Weapon_BFG (edict_t *ent)
+{
+	static int	pause_frames[]	= {39, 45, 50, 55, 0};
+	static int	fire_frames[]	= {9, 17, 0};
+
+	Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire);
+}
+
+
+//======================================================================
+// ROGUE MODS BELOW
+//======================================================================
+
+
+//
+// CHAINFIST
+//
+#define CHAINFIST_REACH 64
+
+void weapon_chainfist_fire (edict_t *ent)
+{
+	vec3_t	offset;
+	vec3_t	forward, right, up;
+	vec3_t	start;
+	int		damage;
+
+	damage = 15;
+	if(deathmatch->value)
+		damage = 30;
+
+	if (is_quad)
+		damage *= damage_multiplier;
+
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+	// kick back
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	// set start point
+	VectorSet(offset, 0, 8, ent->viewheight-4);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	fire_player_melee (ent, start, forward, CHAINFIST_REACH, damage, 100, 1, MOD_CHAINFIST);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	ent->client->ps.gunframe++;
+	ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+}
+
+// this spits out some smoke from the motor. it's a two-stroke, you know.
+void chainfist_smoke (edict_t *ent)
+{
+	vec3_t	tempVec, forward, right, up;
+	vec3_t	offset;
+
+	AngleVectors(ent->client->v_angle, forward, right, up);
+	VectorSet(offset, 8, 8, ent->viewheight -4);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, tempVec);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_CHAINFIST_SMOKE);
+	gi.WritePosition (tempVec);
+	gi.unicast (ent, 0);
+//	gi.multicast (tempVec, MULTICAST_PVS);
+}
+
+//#define HOLD_FRAMES
+
+void Weapon_ChainFist (edict_t *ent)
+{
+	static int	pause_frames[]	= {0};
+	static int	fire_frames[]	= {8, 9, 16, 17, 18, 30, 31, 0};
+
+	// these are caches for the sound index. there's probably a better way to do this.
+//	static int	idle_index;
+//	static int	attack_index;
+	float		chance;
+	int			last_sequence;
+	
+	last_sequence = 0;
+
+	// load chainsaw sounds and store the indexes for later use.
+//	if(!idle_index && !attack_index)
+//	{
+//		idle_index = gi.soundindex("weapons/sawidle.wav");
+//		attack_index = gi.soundindex("weapons/sawhit.wav");
+//	}
+
+	if(ent->client->ps.gunframe == 13 ||
+		ent->client->ps.gunframe == 23)			// end of attack, go idle
+		ent->client->ps.gunframe = 32;
+
+#ifdef HOLD_FRAMES
+	else if(ent->client->ps.gunframe == 9 && ((ent->client->buttons) & BUTTON_ATTACK))
+		ent->client->ps.gunframe = 7;
+	else if(ent->client->ps.gunframe == 18 && ((ent->client->buttons) & BUTTON_ATTACK))
+		ent->client->ps.gunframe = 16;
+#endif
+
+	// holds for idle sequence
+	else if(ent->client->ps.gunframe == 42 && (rand()&7))
+	{
+		if((ent->client->pers.hand != CENTER_HANDED) && qrandom() < 0.4)
+			chainfist_smoke(ent);
+//		ent->client->ps.gunframe = 40;
+	}
+	else if(ent->client->ps.gunframe == 51 && (rand()&7))
+	{
+		if((ent->client->pers.hand != CENTER_HANDED) && qrandom() < 0.4)
+			chainfist_smoke(ent);
+//		ent->client->ps.gunframe = 49;
+	}	
+
+	// set the appropriate weapon sound.
+	if(ent->client->weaponstate == WEAPON_FIRING)
+//		ent->client->weapon_sound = attack_index;
+		ent->client->weapon_sound = gi.soundindex("weapons/sawhit.wav");
+	else if(ent->client->weaponstate == WEAPON_DROPPING)
+		ent->client->weapon_sound = 0;
+	else
+//		ent->client->weapon_sound = idle_index;
+		ent->client->weapon_sound = gi.soundindex("weapons/sawidle.wav");
+
+	Weapon_Generic (ent, 4, 32, 57, 60, pause_frames, fire_frames, weapon_chainfist_fire);
+
+//	gi.dprintf("chainfist %d\n", ent->client->ps.gunframe);
+	if((ent->client->buttons) & BUTTON_ATTACK)
+	{
+		if(ent->client->ps.gunframe == 13 ||
+			ent->client->ps.gunframe == 23 ||
+			ent->client->ps.gunframe == 32)
+		{
+			last_sequence = ent->client->ps.gunframe;
+			ent->client->ps.gunframe = 6;
+		}
+	}
+
+	if (ent->client->ps.gunframe == 6)
+	{
+		chance = qrandom();
+		if(last_sequence == 13)			// if we just did sequence 1, do 2 or 3.
+			chance -= 0.34;
+		else if(last_sequence == 23)	// if we just did sequence 2, do 1 or 3
+			chance += 0.33;
+		else if(last_sequence == 32)	// if we just did sequence 3, do 1 or 2
+		{
+			if(chance >= 0.33)
+				chance += 0.34;
+		}
+
+		if(chance < 0.33)
+			ent->client->ps.gunframe = 14;
+		else if(chance < 0.66)
+			ent->client->ps.gunframe = 24;
+	}
+
+}
+
+//
+// Disintegrator
+//
+
+void weapon_tracker_fire (edict_t *self)
+{
+	vec3_t		forward, right;
+	vec3_t		start;
+	vec3_t		end;
+	vec3_t		offset;
+	edict_t		*enemy;
+	trace_t		tr;
+	int			damage;
+	vec3_t		mins, maxs;
+
+	// PMM - felt a little high at 25
+	if(deathmatch->value)
+		damage = 30;
+	else
+		damage = 45;
+
+	if (is_quad)
+		damage *= damage_multiplier;		//pgm
+
+	VectorSet(mins, -16, -16, -16);
+	VectorSet(maxs, 16, 16, 16);
+	AngleVectors (self->client->v_angle, forward, right, NULL);
+	VectorSet(offset, 24, 8, self->viewheight-8);
+	P_ProjectSource (self->client, self->s.origin, offset, forward, right, start);
+
+	// FIXME - can we shorten this? do we need to?
+	VectorMA (start, 8192, forward, end);
+	enemy = NULL;
+	//PMM - doing two traces .. one point and one box.  
+	tr = gi.trace (start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
+	if(tr.ent != WORLD)
+	{
+		if(tr.ent->svflags & SVF_MONSTER || tr.ent->client || tr.ent->svflags & SVF_DAMAGEABLE)
+		{
+			if(tr.ent->health > 0)
+				enemy = tr.ent;
+		}
+	}
+	else
+	{
+		tr = gi.trace (start, mins, maxs, end, self, MASK_SHOT);
+		if(tr.ent != WORLD)
+		{
+			if(tr.ent->svflags & SVF_MONSTER || tr.ent->client || tr.ent->svflags & SVF_DAMAGEABLE)
+			{
+				if(tr.ent->health > 0)
+					enemy = tr.ent;
+			}
+		}
+	}
+
+	VectorScale (forward, -2, self->client->kick_origin);
+	self->client->kick_angles[0] = -1;
+
+	fire_tracker (self, start, forward, damage, 1000, enemy);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (self-g_edicts);
+	gi.WriteByte (MZ_TRACKER);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(self, start, PNOISE_WEAPON);
+
+	self->client->ps.gunframe++;
+	self->client->pers.inventory[self->client->ammo_index] -= self->client->pers.weapon->quantity;
+}
+
+void Weapon_Disintegrator (edict_t *ent)
+{
+	static int	pause_frames[]	= {14, 19, 23, 0};
+//	static int	fire_frames[]	= {7, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire);
+}
+
+/*
+======================================================================
+
+ETF RIFLE
+
+======================================================================
+*/
+void weapon_etf_rifle_fire (edict_t *ent)
+{
+	vec3_t	forward, right, up;
+	vec3_t	start, tempPt;
+	int		damage;
+	int		kick = 3;
+	int		i;
+	vec3_t	angles;
+	vec3_t	offset;
+
+	if(deathmatch->value)
+		damage = 10;
+	else
+		damage = 10;
+
+	// PGM - adjusted to use the quantity entry in the weapon structure.
+	if(ent->client->pers.inventory[ent->client->ammo_index] < ent->client->pers.weapon->quantity)
+	{
+		VectorClear (ent->client->kick_origin);
+		VectorClear (ent->client->kick_angles);
+		ent->client->ps.gunframe = 8;
+
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+		damage *= damage_multiplier;
+		kick *= damage_multiplier;
+	}
+
+	for(i=0;i<3;i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.85;
+		ent->client->kick_angles[i] = crandom() * 0.85;
+	}
+
+	// get start / end positions
+	VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
+//	AngleVectors (angles, forward, right, NULL);
+//	gi.dprintf("v_angle: %s\n", vtos(ent->client->v_angle));
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+	// FIXME - set correct frames for different offsets.
+
+	if(ent->client->ps.gunframe == 6)					// right barrel
+	{
+//		gi.dprintf("right\n");
+		VectorSet(offset, 15, 8, -8);
+	}
+	else										// left barrel
+	{
+//		gi.dprintf("left\n");
+		VectorSet(offset, 15, 6, -8);
+	}
+	
+	VectorCopy (ent->s.origin, tempPt);
+	tempPt[2] += ent->viewheight;
+	P_ProjectSource2 (ent->client, tempPt, offset, forward, right, up, start);
+//	gi.dprintf("start: %s\n", vtos(start));
+	fire_flechette (ent, start, forward, damage, 750, kick);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_ETF_RIFLE);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	ent->client->ps.gunframe++;
+	ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - 1;
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - 1;
+		ent->client->anim_end = FRAME_attack8;
+	}
+
+}
+
+void Weapon_ETF_Rifle (edict_t *ent)
+{
+	static int	pause_frames[]	= {18, 28, 0};
+	static int	fire_frames[]	= {6, 7, 0};
+//	static int	idle_seq;
+
+	// note - if you change the fire frame number, fix the offset in weapon_etf_rifle_fire.
+
+//	if (!(ent->client->buttons & BUTTON_ATTACK))
+//		ent->client->machinegun_shots = 0;
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->pers.inventory[ent->client->ammo_index] <= 0)
+			ent->client->ps.gunframe = 8;
+	}
+
+	Weapon_Generic (ent, 4, 7, 37, 41, pause_frames, fire_frames, weapon_etf_rifle_fire);
+
+	if(ent->client->ps.gunframe == 8 && (ent->client->buttons & BUTTON_ATTACK))
+		ent->client->ps.gunframe = 6;
+
+//	gi.dprintf("etf rifle %d\n", ent->client->ps.gunframe);
+}
+
+// pgm - this now uses ent->client->pers.weapon->quantity like all the other weapons
+//#define HEATBEAM_AMMO_USE		2		
+#define	HEATBEAM_DM_DMG			15
+#define HEATBEAM_SP_DMG			15
+
+void Heatbeam_Fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right, up;
+	vec3_t		offset;
+	int			damage;
+	int			kick;
+
+	// for comparison, the hyperblaster is 15/20
+	// jim requested more damage, so try 15/15 --- PGM 07/23/98
+	if (deathmatch->value)
+		damage = HEATBEAM_DM_DMG;
+	else
+		damage = HEATBEAM_SP_DMG;
+
+	if (deathmatch->value)  // really knock 'em around in deathmatch
+		kick = 75;
+	else
+		kick = 30;
+
+//	if(ent->client->pers.inventory[ent->client->ammo_index] < HEATBEAM_AMMO_USE)
+//	{
+//		NoAmmoWeaponChange (ent);
+//		return;
+//	}
+
+	ent->client->ps.gunframe++;
+	ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
+
+	if (is_quad)
+	{
+		damage *= damage_multiplier;
+		kick *= damage_multiplier;
+	}
+
+	VectorClear (ent->client->kick_origin);
+	VectorClear (ent->client->kick_angles);
+
+	// get start / end positions
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+// This offset is the "view" offset for the beam start (used by trace)
+	
+	VectorSet(offset, 7, 2, ent->viewheight-3);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	// This offset is the entity offset
+	VectorSet(offset, 2, 7, -3);
+
+	fire_heat (ent, start, forward, offset, damage, kick, false);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_HEATBEAM | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - 1;
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - 1;
+		ent->client->anim_end = FRAME_attack8;
+	}
+
+}
+
+void Weapon_Heatbeam (edict_t *ent)
+{
+//	static int	pause_frames[]	= {38, 43, 51, 61, 0};
+//	static int	fire_frames[]	= {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+	static int	pause_frames[]	= {35, 0};
+//	static int	fire_frames[]	= {9, 0};
+	static int	fire_frames[]	= {9, 10, 11, 12, 0};
+//	static int	attack_index;
+//	static int  off_model, on_model;
+
+//	if ((g_showlogic) && (g_showlogic->value)) {
+//		gi.dprintf ("Frame %d, skin %d\n", ent->client->ps.gunframe, ent->client->ps.gunskin);
+//	}
+	
+//	if (!attack_index)
+//	{
+//		attack_index = gi.soundindex ("weapons/bfg__l1a.wav");
+//		off_model = gi.modelindex ("models/weapons/v_beamer/tris.md2");
+//		on_model = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
+		//ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
+//	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+//		ent->client->weapon_sound = attack_index;
+		ent->client->weapon_sound = gi.soundindex ("weapons/bfg__l1a.wav");
+		if ((ent->client->pers.inventory[ent->client->ammo_index] >= 2) && ((ent->client->buttons) & BUTTON_ATTACK))
+		{
+//			if(ent->client->ps.gunframe >= 9 && ((ent->client->buttons) & BUTTON_ATTACK))
+//			if(ent->client->ps.gunframe >= 12 && ((ent->client->buttons) & BUTTON_ATTACK))
+			if(ent->client->ps.gunframe >= 13)
+			{
+				ent->client->ps.gunframe = 9;
+//				ent->client->ps.gunframe = 8;
+//				ent->client->ps.gunskin = 0;
+//				ent->client->ps.gunindex = on_model;
+				ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
+			}
+			else
+			{
+//				ent->client->ps.gunskin = 1;
+//				ent->client->ps.gunindex = on_model;
+				ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer2/tris.md2");
+			}
+		}
+		else
+		{
+//			ent->client->ps.gunframe = 10;
+			ent->client->ps.gunframe = 13;
+//			ent->client->ps.gunskin = 1;
+//			ent->client->ps.gunindex = off_model;
+			ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2");
+		}
+	}
+	else
+	{
+//		ent->client->ps.gunskin = 1;
+//		ent->client->ps.gunindex = off_model;
+		ent->client->ps.gunindex = gi.modelindex ("models/weapons/v_beamer/tris.md2");
+		ent->client->weapon_sound = 0;
+	}
+
+//	Weapon_Generic (ent, 8, 9, 39, 44, pause_frames, fire_frames, Heatbeam_Fire);
+	Weapon_Generic (ent, 8, 12, 39, 44, pause_frames, fire_frames, Heatbeam_Fire);
+}
--- /dev/null
+++ b/rogue/q_shared.c
@@ -1,0 +1,1080 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define DEG2RAD( a ) ( a * M_PI ) / 180.0F
+
+vec3_t vec3_origin = {0,0,0};
+// ROGUE VERSIONING
+//int rogueid = ROGUE_VERSION_ID;
+// ROGUE
+
+//============================================================================
+
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees )
+{
+	float	m[3][3];
+	float	im[3][3];
+	float	zrot[3][3];
+	float	tmpmat[3][3];
+	float	rot[3][3];
+	int	i;
+	vec3_t vr, vup, vf;
+
+	vf[0] = dir[0];
+	vf[1] = dir[1];
+	vf[2] = dir[2];
+
+	PerpendicularVector( vr, dir );
+	CrossProduct( vr, vf, vup );
+
+	m[0][0] = vr[0];
+	m[1][0] = vr[1];
+	m[2][0] = vr[2];
+
+	m[0][1] = vup[0];
+	m[1][1] = vup[1];
+	m[2][1] = vup[2];
+
+	m[0][2] = vf[0];
+	m[1][2] = vf[1];
+	m[2][2] = vf[2];
+
+	memcpy( im, m, sizeof( im ) );
+
+	im[0][1] = m[1][0];
+	im[0][2] = m[2][0];
+	im[1][0] = m[0][1];
+	im[1][2] = m[2][1];
+	im[2][0] = m[0][2];
+	im[2][1] = m[1][2];
+
+	memset( zrot, 0, sizeof( zrot ) );
+	zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;
+
+	zrot[0][0] = cos( DEG2RAD( degrees ) );
+	zrot[0][1] = sin( DEG2RAD( degrees ) );
+	zrot[1][0] = -sin( DEG2RAD( degrees ) );
+	zrot[1][1] = cos( DEG2RAD( degrees ) );
+
+	R_ConcatRotations( m, zrot, tmpmat );
+	R_ConcatRotations( tmpmat, im, rot );
+
+	for ( i = 0; i < 3; i++ )
+	{
+		dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2];
+	}
+}
+
+void AngleVectors (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;
+	}
+}
+
+
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
+{
+	float d;
+	vec3_t n;
+	float inv_denom;
+
+	inv_denom = 1.0F / DotProduct( normal, normal );
+
+	d = DotProduct( normal, p ) * inv_denom;
+
+	n[0] = normal[0] * inv_denom;
+	n[1] = normal[1] * inv_denom;
+	n[2] = normal[2] * inv_denom;
+
+	dst[0] = p[0] - d * n[0];
+	dst[1] = p[1] - d * n[1];
+	dst[2] = p[2] - d * n[2];
+}
+
+/*
+** assumes "src" is normalized
+*/
+void PerpendicularVector( vec3_t dst, const vec3_t src )
+{
+	int	pos;
+	int i;
+	float minelem = 1.0F;
+	vec3_t tempvec;
+
+	/*
+	** find the smallest magnitude axially aligned vector
+	*/
+	for ( pos = 0, i = 0; i < 3; i++ )
+	{
+		if ( fabs( src[i] ) < minelem )
+		{
+			pos = i;
+			minelem = fabs( src[i] );
+		}
+	}
+	tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+	tempvec[pos] = 1.0F;
+
+	/*
+	** project the point onto the plane defined by src
+	*/
+	ProjectPointOnPlane( dst, tempvec, src );
+
+	/*
+	** normalize the result
+	*/
+	VectorNormalize( dst );
+}
+
+
+
+/*
+================
+R_ConcatRotations
+================
+*/
+void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+}
+
+
+/*
+================
+R_ConcatTransforms
+================
+*/
+void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+				in1[0][2] * in2[2][3] + in1[0][3];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+				in1[1][2] * in2[2][3] + in1[1][3];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+	out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+				in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+
+
+/*
+===============
+LerpAngle
+
+===============
+*/
+float LerpAngle (float a2, float a1, float frac)
+{
+	if (a1 - a2 > 180)
+		a1 -= 360;
+	if (a1 - a2 < -180)
+		a1 += 360;
+	return a2 + frac * (a1 - a2);
+}
+
+
+float	anglemod(float a)
+{
+/*
+	if (a >= 0)
+		a -= 360*(int)(a/360);
+	else
+		a += 360*( 1 + (int)(-a/360) );
+*/
+	a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+	return a;
+}
+
+	int		i;
+	vec3_t	corners[2];
+
+
+// this is the slow, general version
+int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, cplane_t *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;
+}
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+==================
+*/
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, cplane_t *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
+		assert( 0 );
+		break;
+	}
+
+	sides = 0;
+	if (dist1 >= p->dist)
+		sides = 1;
+	if (dist2 < p->dist)
+		sides |= 2;
+
+	assert( sides != 0 );
+
+	return sides;
+}
+
+void ClearBounds (vec3_t mins, vec3_t maxs)
+{
+	mins[0] = mins[1] = mins[2] = 99999;
+	maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs)
+{
+	int		i;
+	vec_t	val;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		val = v[i];
+		if (val < mins[i])
+			mins[i] = val;
+		if (val > maxs[i])
+			maxs[i] = val;
+	}
+}
+
+
+int VectorCompare (vec3_t v1, vec3_t v2)
+{
+	if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2])
+			return 0;
+			
+	return 1;
+}
+
+
+vec_t VectorNormalize (vec3_t v)
+{
+	float	length, ilength;
+
+	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+	length = sqrt (length);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		v[0] *= ilength;
+		v[1] *= ilength;
+		v[2] *= ilength;
+	}
+		
+	return length;
+
+}
+
+vec_t VectorNormalize2 (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);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		out[0] = v[0]*ilength;
+		out[1] = v[1]*ilength;
+		out[2] = v[2]*ilength;
+	}
+		
+	return length;
+
+}
+
+void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
+{
+	vecc[0] = veca[0] + scale*vecb[0];
+	vecc[1] = veca[1] + scale*vecb[1];
+	vecc[2] = veca[2] + scale*vecb[2];
+}
+
+
+vec_t _DotProduct (vec3_t v1, vec3_t v2)
+{
+	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]-vecb[0];
+	out[1] = veca[1]-vecb[1];
+	out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]+vecb[0];
+	out[1] = veca[1]+vecb[1];
+	out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy (vec3_t in, vec3_t out)
+{
+	out[0] = in[0];
+	out[1] = in[1];
+	out[2] = in[2];
+}
+
+void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross)
+{
+	cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
+	cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
+	cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+
+double sqrt(double x);
+
+vec_t VectorLength(vec3_t v)
+{
+	int		i;
+	float	length;
+	
+	length = 0;
+	for (i=0 ; i< 3 ; i++)
+		length += v[i]*v[i];
+	length = sqrt (length);		// FIXME
+
+	return length;
+}
+
+void VectorInverse (vec3_t v)
+{
+	v[0] = -v[0];
+	v[1] = -v[1];
+	v[2] = -v[2];
+}
+
+void VectorScale (vec3_t in, vec_t scale, vec3_t out)
+{
+	out[0] = in[0]*scale;
+	out[1] = in[1]*scale;
+	out[2] = in[2]*scale;
+}
+
+
+//====================================================================================
+
+/*
+============
+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 (char *in, char *out)
+{
+	while (*in && *in != '.')
+		*out++ = *in++;
+	*out = 0;
+}
+
+/*
+============
+COM_FileExtension
+============
+*/
+char *COM_FileExtension (char *in)
+{
+	static char exten[8];
+	int		i;
+
+	while (*in && *in != '.')
+		in++;
+	if (!*in)
+		return "";
+	in++;
+	for (i=0 ; i<7 && *in ; i++,in++)
+		exten[i] = *in;
+	exten[i] = 0;
+	return exten;
+}
+
+/*
+============
+COM_FileBase
+============
+*/
+void COM_FileBase (char *in, char *out)
+{
+	char *s, *s2;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '.')
+		s--;
+	
+	for (s2 = s ; s2 != in && *s2 != '/' ; s2--)
+	;
+	
+	if (s-s2 < 2)
+		out[0] = 0;
+	else
+	{
+		s--;
+		strncpy (out,s2+1, s-s2);
+		out[s-s2] = 0;
+	}
+}
+
+/*
+============
+COM_FilePath
+
+Returns the path up to, but not including the last /
+============
+*/
+void COM_FilePath (char *in, char *out)
+{
+	char *s;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '/')
+		s--;
+
+	strncpy (out,in, s-in);
+	out[s-in] = 0;
+}
+
+
+/*
+==================
+COM_DefaultExtension
+==================
+*/
+void COM_DefaultExtension (char *path, char *extension)
+{
+	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--;
+	}
+
+	strcat (path, extension);
+}
+
+/*
+============================================================================
+
+					BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+
+qboolean	bigendien;
+
+// can't just use function pointers, or dll linkage can
+// mess up when qcommon is included in multiple places
+short	(*_BigShort) (short l);
+short	(*_LittleShort) (short l);
+int		(*_BigLong) (int l);
+int		(*_LittleLong) (int l);
+float	(*_BigFloat) (float l);
+float	(*_LittleFloat) (float l);
+
+short	BigShort(short l){return _BigShort(l);}
+short	LittleShort(short l) {return _LittleShort(l);}
+int		BigLong (int l) {return _BigLong(l);}
+int		LittleLong (int l) {return _LittleLong(l);}
+float	BigFloat (float l) {return _BigFloat(l);}
+float	LittleFloat (float l) {return _LittleFloat(l);}
+
+short   ShortSwap (short l)
+{
+	byte    b1,b2;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+
+	return (b1<<8) + b2;
+}
+
+short	ShortNoSwap (short l)
+{
+	return l;
+}
+
+int    LongSwap (int l)
+{
+	byte    b1,b2,b3,b4;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+	b3 = (l>>16)&255;
+	b4 = (l>>24)&255;
+
+	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int	LongNoSwap (int l)
+{
+	return l;
+}
+
+float FloatSwap (float f)
+{
+	union
+	{
+		float	f;
+		byte	b[4];
+	} dat1, dat2;
+	
+	
+	dat1.f = f;
+	dat2.b[0] = dat1.b[3];
+	dat2.b[1] = dat1.b[2];
+	dat2.b[2] = dat1.b[1];
+	dat2.b[3] = dat1.b[0];
+	return dat2.f;
+}
+
+float FloatNoSwap (float f)
+{
+	return f;
+}
+
+/*
+================
+Swap_Init
+================
+*/
+void Swap_Init (void)
+{
+	byte	swaptest[2] = {1,0};
+
+// set the byte swapping variables in a portable manner	
+	if ( *(short *)swaptest == 1)
+	{
+		bigendien = false;
+		_BigShort = ShortSwap;
+		_LittleShort = ShortNoSwap;
+		_BigLong = LongSwap;
+		_LittleLong = LongNoSwap;
+		_BigFloat = FloatSwap;
+		_LittleFloat = FloatNoSwap;
+	}
+	else
+	{
+		bigendien = true;
+		_BigShort = ShortNoSwap;
+		_LittleShort = ShortSwap;
+		_BigLong = LongNoSwap;
+		_LittleLong = LongSwap;
+		_BigFloat = FloatNoSwap;
+		_LittleFloat = FloatSwap;
+	}
+
+}
+
+
+
+/*
+============
+va
+
+does a varargs printf into a temp buffer, so I don't need to have
+varargs versions of all text functions.
+FIXME: make this buffer size safe someday
+============
+*/
+char	*va(char *format, ...)
+{
+	va_list		argptr;
+	static char		string[1024];
+	
+	va_start (argptr, format);
+	vsprintf (string, format,argptr);
+	va_end (argptr);
+
+	return string;	
+}
+
+
+char	com_token[MAX_TOKEN_CHARS];
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+==============
+*/
+char *COM_Parse (char **data_p)
+{
+	int		c;
+	int		len;
+	char	*data;
+
+	data = *data_p;
+	len = 0;
+	com_token[0] = 0;
+	
+	if (!data)
+	{
+		*data_p = NULL;
+		return "";
+	}
+		
+// skip whitespace
+skipwhite:
+	while ( (c = *data) <= ' ')
+	{
+		if (c == 0)
+		{
+			*data_p = NULL;
+			return "";
+		}
+		data++;
+	}
+	
+// skip // comments
+	if (c=='/' && data[1] == '/')
+	{
+		while (*data && *data != '\n')
+			data++;
+		goto skipwhite;
+	}
+	
+// handle quoted strings specially
+	if (c == '\"')
+	{
+		data++;
+		while (1)
+		{
+			c = *data++;
+			if (c=='\"' || !c)
+			{
+				com_token[len] = 0;
+				*data_p = data;
+				return com_token;
+			}
+			if (len < MAX_TOKEN_CHARS)
+			{
+				com_token[len] = c;
+				len++;
+			}
+		}
+	}
+
+// parse a regular word
+	do
+	{
+		if (len < MAX_TOKEN_CHARS)
+		{
+			com_token[len] = c;
+			len++;
+		}
+		data++;
+		c = *data;
+	} while (c>32);
+
+	if (len == MAX_TOKEN_CHARS)
+	{
+//		Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
+		len = 0;
+	}
+	com_token[len] = 0;
+
+	*data_p = data;
+	return com_token;
+}
+
+
+/*
+===============
+Com_PageInMemory
+
+===============
+*/
+int	paged_total;
+
+void Com_PageInMemory (byte *buffer, int size)
+{
+	int		i;
+
+	for (i=size-1 ; i>0 ; i-=4096)
+		paged_total += buffer[i];
+}
+
+
+
+/*
+============================================================================
+
+					LIBRARY REPLACEMENT FUNCTIONS
+
+============================================================================
+*/
+
+
+void Com_sprintf (char *dest, int size, char *fmt, ...)
+{
+	int		len;
+	va_list		argptr;
+	char	bigbuffer[0x10000];
+
+	va_start (argptr,fmt);
+	len = vsprintf (bigbuffer,fmt,argptr);
+	va_end (argptr);
+	if (len >= size)
+		Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
+	strncpy (dest, bigbuffer, size-1);
+}
+
+/*
+=====================================================================
+
+  INFO STRINGS
+
+=====================================================================
+*/
+
+/*
+===============
+Info_ValueForKey
+
+Searches the string for the given
+key and returns the associated value, or an empty string.
+===============
+*/
+char *Info_ValueForKey (char *s, char *key)
+{
+	char	pkey[512];
+	static	char value[2][512];	// use two buffers so compares
+								// work without stomping on each other
+	static	int	valueindex;
+	char	*o;
+	
+	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)
+		{
+			if (!*s)
+				return "";
+			*o++ = *s++;
+		}
+		*o = 0;
+
+		if (!strcmp (key, pkey) )
+			return value[valueindex];
+
+		if (!*s)
+			return "";
+		s++;
+	}
+}
+
+void Info_RemoveKey (char *s, char *key)
+{
+	char	*start;
+	char	pkey[512];
+	char	value[512];
+	char	*o;
+
+	if (strstr (key, "\\"))
+	{
+//		Com_Printf ("Can't use a key with a \\\n");
+		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 (char *s)
+{
+	if (strstr (s, "\""))
+		return false;
+	if (strstr (s, ";"))
+		return false;
+	return true;
+}
+
+void Info_SetValueForKey (char *s, char *key, char *value)
+{
+	char	newi[MAX_INFO_STRING], *v;
+	int		c;
+	int		maxsize = MAX_INFO_STRING;
+
+	if (strstr (key, "\\") || strstr (value, "\\") )
+	{
+		Com_Printf ("Can't use keys or values with a \\\n");
+		return;
+	}
+
+	if (strstr (key, ";") )
+	{
+		Com_Printf ("Can't use keys or values with a semicolon\n");
+		return;
+	}
+
+	if (strstr (key, "\"") || strstr (value, "\"") )
+	{
+		Com_Printf ("Can't use keys or values with a \"\n");
+		return;
+	}
+
+	if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1)
+	{
+		Com_Printf ("Keys and values must be < 64 characters.\n");
+		return;
+	}
+	Info_RemoveKey (s, key);
+	if (!value || !strlen(value))
+		return;
+
+	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+	if (strlen(newi) + strlen(s) > maxsize)
+	{
+		Com_Printf ("Info string length exceeded\n");
+		return;
+	}
+
+	// only copy ascii values
+	s += strlen(s);
+	v = newi;
+	while (*v)
+	{
+		c = *v++;
+		c &= 127;		// strip high bits
+		if (c >= 32 && c < 127)
+			*s++ = c;
+	}
+	*s = 0;
+}
+
+//====================================================================
+
+
--- /dev/null
+++ b/xatrix/g_ai.c
@@ -1,0 +1,1100 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+qboolean FindTarget (edict_t *self);
+extern cvar_t	*maxclients;
+
+qboolean ai_checkattack (edict_t *self, float dist);
+
+qboolean	enemy_vis;
+qboolean	enemy_infront;
+int			enemy_range;
+float		enemy_yaw;
+
+//============================================================================
+
+
+/*
+=================
+AI_SetSightClient
+
+Called once each frame to set level.sight_client to the
+player to be checked for in findtarget.
+
+If all clients are either dead or in notarget, sight_client
+will be null.
+
+In coop games, sight_client will cycle between the clients.
+=================
+*/
+void AI_SetSightClient (void)
+{
+	edict_t	*ent;
+	int		start, check;
+
+	if (level.sight_client == NULL)
+		start = 1;
+	else
+		start = level.sight_client - g_edicts;
+
+	check = start;
+	while (1)
+	{
+		check++;
+		if (check > game.maxclients)
+			check = 1;
+		ent = &g_edicts[check];
+		if (ent->inuse
+			&& ent->health > 0
+			&& !(ent->flags & FL_NOTARGET) )
+		{
+			level.sight_client = ent;
+			return;		// got one
+		}
+		if (check == start)
+		{
+			level.sight_client = NULL;
+			return;		// nobody to see
+		}
+	}
+}
+
+//============================================================================
+
+/*
+=============
+ai_move
+
+Move the specified distance at current facing.
+This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
+==============
+*/
+void ai_move (edict_t *self, float dist)
+{
+	M_walkmove (self, self->s.angles[YAW], dist);
+}
+
+
+/*
+=============
+ai_stand
+
+Used for standing around and looking for players
+Distance is for slight position adjustments needed by the animations
+==============
+*/
+void ai_stand (edict_t *self, float dist)
+{
+	vec3_t	v;
+
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		if (self->enemy)
+		{
+			VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+			self->ideal_yaw = vectoyaw(v);
+			if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+			{
+				self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+				self->monsterinfo.run (self);
+			}
+			M_ChangeYaw (self);
+			ai_checkattack (self, 0);
+		}
+		else
+			FindTarget (self);
+		return;
+	}
+
+	if (FindTarget (self))
+		return;
+	
+	if (level.time > self->monsterinfo.pausetime)
+	{
+		self->monsterinfo.walk (self);
+		return;
+	}
+
+	if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.idle (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_walk
+
+The monster is walking it's beat
+=============
+*/
+void ai_walk (edict_t *self, float dist)
+{
+	M_MoveToGoal (self, dist);
+
+	// check for noticing a player
+	if (FindTarget (self))
+		return;
+
+	if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
+	{
+		if (self->monsterinfo.idle_time)
+		{
+			self->monsterinfo.search (self);
+			self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+		}
+		else
+		{
+			self->monsterinfo.idle_time = level.time + qrandom() * 15;
+		}
+	}
+}
+
+
+/*
+=============
+ai_charge
+
+Turns towards target and advances
+Use this call with a distnace of 0 to replace ai_face
+==============
+*/
+void ai_charge (edict_t *self, float dist)
+{
+	vec3_t	v;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, v);
+	self->ideal_yaw = vectoyaw(v);
+	M_ChangeYaw (self);
+
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+}
+
+
+/*
+=============
+ai_turn
+
+don't move, but turn towards ideal_yaw
+Distance is for slight position adjustments needed by the animations
+=============
+*/
+void ai_turn (edict_t *self, float dist)
+{
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	if (FindTarget (self))
+		return;
+	
+	M_ChangeYaw (self);
+}
+
+
+/*
+
+.enemy
+Will be world if not currently angry at anyone.
+
+.movetarget
+The next path spot to walk toward.  If .enemy, ignore .movetarget.
+When an enemy is killed, the monster will try to return to it's path.
+
+.hunt_time
+Set to time + something when the player is in sight, but movement straight for
+him is blocked.  This causes the monster to use wall following code for
+movement direction instead of sighting on the player.
+
+.ideal_yaw
+A yaw angle of the intended direction, which will be turned towards at up
+to 45 deg / state.  If the enemy is in view and hunt_time is not active,
+this will be the exact line towards the enemy.
+
+.pausetime
+A monster will leave it's stand state and head towards it's .movetarget when
+time > .pausetime.
+
+walkmove(angle, speed) primitive is all or nothing
+*/
+
+/*
+=============
+range
+
+returns the range catagorization of an entity reletive to self
+0	melee range, will become hostile even if back is turned
+1	visibility and infront, or visibility and show hostile
+2	infront and show hostile
+3	only triggered by damage
+=============
+*/
+int range (edict_t *self, edict_t *other)
+{
+	vec3_t	v;
+	float	len;
+
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	len = VectorLength (v);
+	if (len < MELEE_DISTANCE)
+		return RANGE_MELEE;
+	if (len < 500)
+		return RANGE_NEAR;
+	if (len < 1000)
+		return RANGE_MID;
+	return RANGE_FAR;
+}
+
+/*
+=============
+visible
+
+returns 1 if the entity is visible to self, even if not infront ()
+=============
+*/
+qboolean visible (edict_t *self, edict_t *other)
+{
+	vec3_t	spot1;
+	vec3_t	spot2;
+	trace_t	trace;
+
+	VectorCopy (self->s.origin, spot1);
+	spot1[2] += self->viewheight;
+	VectorCopy (other->s.origin, spot2);
+	spot2[2] += other->viewheight;
+	trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
+	
+	if (trace.fraction == 1.0)
+		return true;
+	return false;
+}
+
+
+/*
+=============
+infront
+
+returns 1 if the entity is in front (in sight) of self
+=============
+*/
+qboolean infront (edict_t *self, edict_t *other)
+{
+	vec3_t	vec;
+	float	dot;
+	vec3_t	forward;
+	
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	VectorSubtract (other->s.origin, self->s.origin, vec);
+	VectorNormalize (vec);
+	dot = DotProduct (vec, forward);
+	
+	if (dot > 0.3)
+		return true;
+	return false;
+}
+
+
+//============================================================================
+
+void HuntTarget (edict_t *self)
+{
+	vec3_t	vec;
+
+	self->goalentity = self->enemy;
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.stand (self);
+	else
+		self->monsterinfo.run (self);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	self->ideal_yaw = vectoyaw(vec);
+	// wait a while before first attack
+	if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
+		AttackFinished (self, 1);
+}
+
+void FoundTarget (edict_t *self)
+{
+	// let other monsters see this monster for a while
+	if (self->enemy->client)
+	{
+		level.sight_entity = self;
+		level.sight_entity_framenum = level.framenum;
+		level.sight_entity->light_level = 128;
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+	VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
+	self->monsterinfo.trail_time = level.time;
+
+	if (!self->combattarget)
+	{
+		HuntTarget (self);
+		return;
+	}
+
+	self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
+	if (!self->movetarget)
+	{
+		self->goalentity = self->movetarget = self->enemy;
+		HuntTarget (self);
+		gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
+		return;
+	}
+
+	// clear out our combattarget, these are a one shot deal
+	self->combattarget = NULL;
+	self->monsterinfo.aiflags |= AI_COMBAT_POINT;
+
+	// clear the targetname, that point is ours!
+	self->movetarget->targetname = NULL;
+	self->monsterinfo.pausetime = 0;
+
+	// run for it
+	self->monsterinfo.run (self);
+}
+
+
+/*
+===========
+FindTarget
+
+Self is currently not attacking anything, so try to find a target
+
+Returns TRUE if an enemy was sighted
+
+When a player fires a missile, the point of impact becomes a fakeplayer so
+that monsters that see the impact will respond as if they had seen the
+player.
+
+To avoid spending too much time, only a single client (or fakeclient) is
+checked each frame.  This means multi player games will have slightly
+slower noticing monsters.
+============
+*/
+qboolean FindTarget (edict_t *self)
+{
+	edict_t		*client;
+	qboolean	heardit;
+	int			r;
+
+	if (self->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
+		{
+			if (strcmp(self->goalentity->classname, "target_actor") == 0)
+				return false;
+		}
+
+		//FIXME look for monsters?
+		return false;
+	}
+
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+		return false;
+
+// if the first spawnflag bit is set, the monster will only wake up on
+// really seeing the player, not another monster getting angry or hearing
+// something
+
+// revised behavior so they will wake up if they "see" a player make a noise
+// but not weapon impact/explosion noises
+
+	heardit = false;
+	if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sight_entity;
+		if (client->enemy == self->enemy)
+		{
+			return false;
+		}
+	}
+	else if (level.sound_entity_framenum >= (level.framenum - 1))
+	{
+		client = level.sound_entity;
+		heardit = true;
+	}
+	else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
+	{
+		client = level.sound2_entity;
+		heardit = true;
+	}
+	else
+	{
+		client = level.sight_client;
+		if (!client)
+			return false;	// no clients to get mad at
+	}
+
+	// if the entity went away, forget it
+	if (!client->inuse)
+		return false;
+
+	if (client == self->enemy)
+		return true;	// JDC false;
+
+	if (client->client)
+	{
+		if (client->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (client->svflags & SVF_MONSTER)
+	{
+		if (!client->enemy)
+			return false;
+		if (client->enemy->flags & FL_NOTARGET)
+			return false;
+	}
+	else if (heardit)
+	{
+		if (client->owner->flags & FL_NOTARGET)
+			return false;
+	}
+	else
+		return false;
+
+	if (!heardit)
+	{
+		r = range (self, client);
+
+		if (r == RANGE_FAR)
+			return false;
+
+// this is where we would check invisibility
+
+		// is client in an spot too dark to be seen?
+		if (client->light_level <= 5)
+			return false;
+
+		if (!visible (self, client))
+		{
+			return false;
+		}
+
+		if (r == RANGE_NEAR)
+		{
+			if (client->show_hostile < level.time && !infront (self, client))
+			{
+				return false;
+			}
+		}
+		else if (r == RANGE_MID)
+		{
+			if (!infront (self, client))
+			{
+				return false;
+			}
+		}
+
+		self->enemy = client;
+
+		if (strcmp(self->enemy->classname, "player_noise") != 0)
+		{
+			self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+			if (!self->enemy->client)
+			{
+				self->enemy = self->enemy->enemy;
+				if (!self->enemy->client)
+				{
+					self->enemy = NULL;
+					return false;
+				}
+			}
+		}
+	}
+	else	// heardit
+	{
+		vec3_t	temp;
+
+		if (self->spawnflags & 1)
+		{
+			if (!visible (self, client))
+				return false;
+		}
+		else
+		{
+			if (!gi.inPHS(self->s.origin, client->s.origin))
+				return false;
+		}
+
+		VectorSubtract (client->s.origin, self->s.origin, temp);
+
+		if (VectorLength(temp) > 1000)	// too far to hear
+		{
+			return false;
+		}
+
+		// check area portals - if they are different and not connected then we can't hear it
+		if (client->areanum != self->areanum)
+			if (!gi.AreasConnected(self->areanum, client->areanum))
+				return false;
+
+		self->ideal_yaw = vectoyaw(temp);
+		M_ChangeYaw (self);
+
+		// hunt the sound for a bit; hopefully find the real player
+		self->monsterinfo.aiflags |= AI_SOUND_TARGET;
+		self->enemy = client;
+	}
+
+//
+// got one
+//
+	FoundTarget (self);
+
+	if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
+		self->monsterinfo.sight (self, self->enemy);
+
+	return true;
+}
+
+
+//=============================================================================
+
+/*
+============
+FacingIdeal
+
+============
+*/
+qboolean FacingIdeal(edict_t *self)
+{
+	float	delta;
+
+	delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
+	if (delta > 45 && delta < 315)
+		return false;
+	return true;
+}
+
+
+//=============================================================================
+
+qboolean M_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	float	chance;
+	trace_t	tr;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+	
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		// don't always melee in easy mode
+		if (skill->value == 0 && (rand()&3) )
+			return false;
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.2;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.1;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.02;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (skill->value == 0)
+		chance *= 0.5;
+	else if (skill->value >= 2)
+		chance *= 2;
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+/*
+=============
+ai_run_melee
+
+Turn and close until within an angle to launch a melee attack
+=============
+*/
+void ai_run_melee(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.melee (self);
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+}
+
+
+/*
+=============
+ai_run_missile
+
+Turn in place until within an angle to launch a missile attack
+=============
+*/
+void ai_run_missile(edict_t *self)
+{
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (FacingIdeal(self))
+	{
+		self->monsterinfo.attack (self);
+		self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+};
+
+
+/*
+=============
+ai_run_slide
+
+Strafe sideways, but stay at aproximately the same range
+=============
+*/
+void ai_run_slide(edict_t *self, float distance)
+{
+	float	ofs;
+	
+	self->ideal_yaw = enemy_yaw;
+	M_ChangeYaw (self);
+
+	if (self->monsterinfo.lefty)
+		ofs = 90;
+	else
+		ofs = -90;
+	
+	if (M_walkmove (self, self->ideal_yaw + ofs, distance))
+		return;
+		
+	self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
+	M_walkmove (self, self->ideal_yaw - ofs, distance);
+}
+
+
+/*
+=============
+ai_checkattack
+
+Decides if we're going to attack or do something else
+used by ai_run and ai_stand
+=============
+*/
+qboolean ai_checkattack (edict_t *self, float)
+{
+	vec3_t		temp;
+	qboolean	hesDeadJim;
+
+// this causes monsters to run blindly to the combat point w/o firing
+	if (self->goalentity)
+	{
+		if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+			return false;
+
+		if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+		{
+			if ((level.time - self->enemy->teleport_time) > 5.0)
+			{
+				if (self->goalentity == self->enemy)
+					if (self->movetarget)
+						self->goalentity = self->movetarget;
+					else
+						self->goalentity = NULL;
+				self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+				if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
+					self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			}
+			else
+			{
+				self->show_hostile = level.time + 1;
+				return false;
+			}
+		}
+	}
+
+	enemy_vis = false;
+
+// see if the enemy is dead
+	hesDeadJim = false;
+	if ((!self->enemy) || (!self->enemy->inuse))
+	{
+		hesDeadJim = true;
+	}
+	else if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (self->enemy->health > 0)
+		{
+			hesDeadJim = true;
+			self->monsterinfo.aiflags &= ~AI_MEDIC;
+		}
+	}
+	else
+	{
+		if (self->monsterinfo.aiflags & AI_BRUTAL)
+		{
+			if (self->enemy->health <= -80)
+				hesDeadJim = true;
+		}
+		else
+		{
+			if (self->enemy->health <= 0)
+				hesDeadJim = true;
+		}
+	}
+
+	if (hesDeadJim)
+	{
+		self->enemy = NULL;
+	// FIXME: look all around for other targets
+		if (self->oldenemy && self->oldenemy->health > 0)
+		{
+			self->enemy = self->oldenemy;
+			self->oldenemy = NULL;
+			HuntTarget (self);
+		}
+		else
+		{
+			if (self->movetarget)
+			{
+				self->goalentity = self->movetarget;
+				self->monsterinfo.walk (self);
+			}
+			else
+			{
+				// we need the pausetime otherwise the stand code
+				// will just revert to walking with no target and
+				// the monsters will wonder around aimlessly trying
+				// to hunt the world entity
+				self->monsterinfo.pausetime = level.time + 100000000;
+				self->monsterinfo.stand (self);
+			}
+			return true;
+		}
+	}
+
+	self->show_hostile = level.time + 1;		// wake up other monsters
+
+// check knowledge of enemy
+	enemy_vis = visible(self, self->enemy);
+	if (enemy_vis)
+	{
+		self->monsterinfo.search_time = level.time + 5;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+	}
+
+// look for other coop players here
+//	if (coop && self->monsterinfo.search_time < level.time)
+//	{
+//		if (FindTarget (self))
+//			return true;
+//	}
+
+	enemy_infront = infront(self, self->enemy);
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+
+	// JDC self->ideal_yaw = enemy_yaw;
+
+	if (self->monsterinfo.attack_state == AS_MISSILE)
+	{
+		ai_run_missile (self);
+		return true;
+	}
+	if (self->monsterinfo.attack_state == AS_MELEE)
+	{
+		ai_run_melee (self);
+		return true;
+	}
+
+	// if enemy is not currently visible, we will never attack
+	if (!enemy_vis)
+		return false;
+
+	return self->monsterinfo.checkattack (self);
+}
+
+
+/*
+=============
+ai_run
+
+The monster has an enemy it is trying to kill
+=============
+*/
+void ai_run (edict_t *self, float dist)
+{
+	vec3_t		v;
+	edict_t		*tempgoal;
+	edict_t		*save;
+	qboolean	new;
+	edict_t		*marker;
+	float		d1, d2;
+	trace_t		tr;
+	vec3_t		v_forward, v_right;
+	float		left, center, right;
+	vec3_t		left_target, right_target;
+
+	// if we're going to a combat point, just proceed
+	if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
+	{
+		M_MoveToGoal (self, dist);
+		return;
+	}
+
+	if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
+	{
+		VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+		if (VectorLength(v) < 64)
+		{
+			self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
+			self->monsterinfo.stand (self);
+			return;
+		}
+
+		M_MoveToGoal (self, dist);
+
+		if (!FindTarget (self))
+			return;
+	}
+
+	if (ai_checkattack (self, dist))
+		return;
+
+	if (self->monsterinfo.attack_state == AS_SLIDING)
+	{
+		ai_run_slide (self, dist);
+		return;
+	}
+
+	if (enemy_vis)
+	{
+//		if (self.aiflags & AI_LOST_SIGHT)
+//			dprint("regained sight\n");
+		M_MoveToGoal (self, dist);
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+		VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
+		self->monsterinfo.trail_time = level.time;
+		return;
+	}
+
+	// coop will change to another enemy if visible
+	if (coop->value)
+	{	// FIXME: insane guys get mad with this, which causes crashes!
+		if (FindTarget (self))
+			return;
+	}
+
+	if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
+	{
+		M_MoveToGoal (self, dist);
+		self->monsterinfo.search_time = 0;
+//		dprint("search timeout\n");
+		return;
+	}
+
+	save = self->goalentity;
+	tempgoal = G_Spawn();
+	self->goalentity = tempgoal;
+
+	new = false;
+
+	if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
+	{
+		// just lost sight of the player, decide where to go first
+//		dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
+		self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
+		self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
+		new = true;
+	}
+
+	if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
+	{
+		self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
+//		dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
+
+		// give ourself more time since we got this far
+		self->monsterinfo.search_time = level.time + 5;
+
+		if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
+		{
+//			dprint("was temp goal; retrying original\n");
+			self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
+			marker = NULL;
+			VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
+			new = true;
+		}
+		else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
+		{
+			self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
+			marker = PlayerTrail_PickFirst (self);
+		}
+		else
+		{
+			marker = PlayerTrail_PickNext (self);
+		}
+
+		if (marker)
+		{
+			VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
+			self->monsterinfo.trail_time = marker->timestamp;
+			self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
+//			dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
+
+//			debug_drawline(self.origin, self.last_sighting, 52);
+			new = true;
+		}
+	}
+
+	VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
+	d1 = VectorLength(v);
+	if (d1 <= dist)
+	{
+		self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
+		dist = d1;
+	}
+
+	VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
+
+	if (new)
+	{
+//		gi.dprintf("checking for course correction\n");
+
+		tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
+		if (tr.fraction < 1)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			d1 = VectorLength(v);
+			center = tr.fraction;
+			d2 = d1 * ((center+1)/2);
+			self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+			AngleVectors(self->s.angles, v_forward, v_right, NULL);
+
+			VectorSet(v, d2, -16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
+			left = tr.fraction;
+
+			VectorSet(v, d2, 16, 0);
+			G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+			tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
+			right = tr.fraction;
+
+			center = (d1*center)/d2;
+			if (left >= center && left > right)
+			{
+				if (left < 1)
+				{
+					VectorSet(v, d2 * left * 0.5, -16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (left_target, self->goalentity->s.origin);
+				VectorCopy (left_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted left\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+			else if (right >= center && right > left)
+			{
+				if (right < 1)
+				{
+					VectorSet(v, d2 * right * 0.5, 16, 0);
+					G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
+//					gi.dprintf("incomplete path, go part way and adjust again\n");
+				}
+				VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
+				self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
+				VectorCopy (right_target, self->goalentity->s.origin);
+				VectorCopy (right_target, self->monsterinfo.last_sighting);
+				VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+				self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
+//				gi.dprintf("adjusted right\n");
+//				debug_drawline(self.origin, self.last_sighting, 152);
+			}
+		}
+//		else gi.dprintf("course was fine\n");
+	}
+
+	M_MoveToGoal (self, dist);
+
+	G_FreeEdict(tempgoal);
+
+	if (self)
+		self->goalentity = save;
+}
--- /dev/null
+++ b/xatrix/g_chase.c
@@ -1,0 +1,160 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+void UpdateChaseCam(edict_t *ent)
+{
+	vec3_t o, ownerv, goal;
+	edict_t *targ;
+	vec3_t forward, right;
+	trace_t trace;
+	int i;
+	vec3_t oldgoal;
+	vec3_t angles;
+
+	// is our chase target gone?
+	if (!ent->client->chase_target->inuse
+		|| ent->client->chase_target->client->resp.spectator) {
+		edict_t *old = ent->client->chase_target;
+		ChaseNext(ent);
+		if (ent->client->chase_target == old) {
+			ent->client->chase_target = NULL;
+			ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+			return;
+		}
+	}
+
+	targ = ent->client->chase_target;
+
+	VectorCopy(targ->s.origin, ownerv);
+	VectorCopy(ent->s.origin, oldgoal);
+
+	ownerv[2] += targ->viewheight;
+
+	VectorCopy(targ->client->v_angle, angles);
+	if (angles[PITCH] > 56)
+		angles[PITCH] = 56;
+	AngleVectors (angles, forward, right, NULL);
+	VectorNormalize(forward);
+	VectorMA(ownerv, -30, forward, o);
+
+	if (o[2] < targ->s.origin[2] + 20)
+		o[2] = targ->s.origin[2] + 20;
+
+	// jump animation lifts
+	if (!targ->groundentity)
+		o[2] += 16;
+
+	trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+
+	VectorCopy(trace.endpos, goal);
+
+	VectorMA(goal, 2, forward, goal);
+
+	// pad for floors and ceilings
+	VectorCopy(goal, o);
+	o[2] += 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] -= 6;
+	}
+
+	VectorCopy(goal, o);
+	o[2] -= 6;
+	trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID);
+	if (trace.fraction < 1) {
+		VectorCopy(trace.endpos, goal);
+		goal[2] += 6;
+	}
+
+	if (targ->deadflag)
+		ent->client->ps.pmove.pm_type = PM_DEAD;
+	else
+		ent->client->ps.pmove.pm_type = PM_FREEZE;
+
+	VectorCopy(goal, ent->s.origin);
+	for (i=0 ; i<3 ; i++)
+		ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]);
+
+	if (targ->deadflag) {
+		ent->client->ps.viewangles[ROLL] = 40;
+		ent->client->ps.viewangles[PITCH] = -15;
+		ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
+	} else {
+		VectorCopy(targ->client->v_angle, ent->client->ps.viewangles);
+		VectorCopy(targ->client->v_angle, ent->client->v_angle);
+	}
+
+	ent->viewheight = 0;
+	ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
+	gi.linkentity(ent);
+}
+
+void ChaseNext(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i++;
+		if (i > maxclients->value)
+			i = 1;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (!e->client->resp.spectator)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
+
+void ChasePrev(edict_t *ent)
+{
+	int i;
+	edict_t *e;
+
+	if (!ent->client->chase_target)
+		return;
+
+	i = ent->client->chase_target - g_edicts;
+	do {
+		i--;
+		if (i < 1)
+			i = maxclients->value;
+		e = g_edicts + i;
+		if (!e->inuse)
+			continue;
+		if (!e->client->resp.spectator)
+			break;
+	} while (e != ent->client->chase_target);
+
+	ent->client->chase_target = e;
+	ent->client->update_chase = true;
+}
+
+void GetChaseTarget(edict_t *ent)
+{
+	int i;
+	edict_t *other;
+
+	for (i = 1; i <= maxclients->value; i++) {
+		other = g_edicts + i;
+		if (other->inuse && !other->client->resp.spectator) {
+			ent->client->chase_target = other;
+			ent->client->update_chase = true;
+			UpdateChaseCam(ent);
+			return;
+		}
+	}
+	gi.centerprintf(ent, "No other players to chase.");
+}
+
--- /dev/null
+++ b/xatrix/g_cmds.c
@@ -1,0 +1,1064 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+char *ClientTeam (edict_t *ent)
+{
+	char		*p;
+	static char	value[512];
+
+	value[0] = 0;
+
+	if (!ent->client)
+		return value;
+
+	strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin"));
+	p = strchr(value, '/');
+	if (!p)
+		return value;
+
+	if ((int)(dmflags->value) & DF_MODELTEAMS)
+	{
+		*p = 0;
+		return value;
+	}
+
+	// if ((int)(dmflags->value) & DF_SKINTEAMS)
+	return ++p;
+}
+
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2)
+{
+	char	ent1Team [512];
+	char	ent2Team [512];
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		return false;
+
+	strcpy (ent1Team, ClientTeam (ent1));
+	strcpy (ent2Team, ClientTeam (ent2));
+
+	if (strcmp(ent1Team, ent2Team) == 0)
+		return true;
+	return false;
+}
+
+
+void SelectNextItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (cl->chase_target) {
+		ChaseNext(ent);
+		return;
+	}
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void SelectPrevItem (edict_t *ent, int itflags)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (cl->chase_target) {
+		ChasePrev(ent);
+		return;
+	}
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (!(it->flags & itflags))
+			continue;
+
+		cl->pers.selected_item = index;
+		return;
+	}
+
+	cl->pers.selected_item = -1;
+}
+
+void ValidateSelectedItem (edict_t *ent)
+{
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	if (cl->pers.inventory[cl->pers.selected_item])
+		return;		// valid
+
+	SelectNextItem (ent, -1);
+}
+
+
+//=================================================================================
+
+/*
+==================
+Cmd_Give_f
+
+Give items to a client
+==================
+*/
+void Cmd_Give_f (edict_t *ent)
+{
+	char		*name;
+	gitem_t		*it;
+	int			index;
+	int			i;
+	qboolean	give_all;
+	edict_t		*it_ent;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	name = gi.args();
+
+	if (cistrcmp(name, "all") == 0)
+		give_all = true;
+	else
+		give_all = false;
+
+	if (give_all || cistrcmp(gi.argv(1), "health") == 0)
+	{
+		if (gi.argc() == 3)
+			ent->health = atoi(gi.argv(2));
+		else
+			ent->health = ent->max_health;
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "weapons") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_WEAPON))
+				continue;
+			ent->client->pers.inventory[i] += 1;
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "ammo") == 0)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (!(it->flags & IT_AMMO))
+				continue;
+			Add_Ammo (ent, it, 1000);
+		}
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "armor") == 0)
+	{
+		gitem_armor_t	*info;
+
+		it = FindItem("Jacket Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Combat Armor");
+		ent->client->pers.inventory[ITEM_INDEX(it)] = 0;
+
+		it = FindItem("Body Armor");
+		info = (gitem_armor_t *)it->info;
+		ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count;
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all || cistrcmp(name, "Power Shield") == 0)
+	{
+		it = FindItem("Power Shield");
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+
+		if (!give_all)
+			return;
+	}
+
+	if (give_all)
+	{
+		for (i=0 ; i<game.num_items ; i++)
+		{
+			it = itemlist + i;
+			if (!it->pickup)
+				continue;
+			if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO))
+				continue;
+			ent->client->pers.inventory[i] = 1;
+		}
+		return;
+	}
+
+	it = FindItem (name);
+	if (!it)
+	{
+		name = gi.argv(1);
+		it = FindItem (name);
+		if (!it)
+		{
+			gi.cprintf (ent, PRINT_HIGH, "unknown item\n");
+			return;
+		}
+	}
+
+	if (!it->pickup)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n");
+		return;
+	}
+
+	index = ITEM_INDEX(it);
+
+	if (it->flags & IT_AMMO)
+	{
+		if (gi.argc() == 3)
+			ent->client->pers.inventory[index] = atoi(gi.argv(2));
+		else
+			ent->client->pers.inventory[index] += it->quantity;
+	}
+	else
+	{
+		it_ent = G_Spawn();
+		it_ent->classname = it->classname;
+		SpawnItem (it_ent, it);
+		Touch_Item (it_ent, ent, NULL, NULL);
+		if (it_ent->inuse)
+			G_FreeEdict(it_ent);
+	}
+}
+
+
+/*
+==================
+Cmd_God_f
+
+Sets client to godmode
+
+argv(0) god
+==================
+*/
+void Cmd_God_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_GODMODE;
+	if (!(ent->flags & FL_GODMODE) )
+		msg = "godmode OFF\n";
+	else
+		msg = "godmode ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Notarget_f
+
+Sets client to notarget
+
+argv(0) notarget
+==================
+*/
+void Cmd_Notarget_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	ent->flags ^= FL_NOTARGET;
+	if (!(ent->flags & FL_NOTARGET) )
+		msg = "notarget OFF\n";
+	else
+		msg = "notarget ON\n";
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Noclip_f
+
+argv(0) noclip
+==================
+*/
+void Cmd_Noclip_f (edict_t *ent)
+{
+	char	*msg;
+
+	if (deathmatch->value && !sv_cheats->value)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n");
+		return;
+	}
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+	{
+		ent->movetype = MOVETYPE_WALK;
+		msg = "noclip OFF\n";
+	}
+	else
+	{
+		ent->movetype = MOVETYPE_NOCLIP;
+		msg = "noclip ON\n";
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, msg);
+}
+
+
+/*
+==================
+Cmd_Use_f
+
+Use an inventory item
+==================
+*/
+
+void Cmd_Use_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		// RAFAEL
+		if (strcmp (it->pickup_name, "HyperBlaster") == 0)
+		{
+			it = FindItem ("Ionripper");
+			index = ITEM_INDEX (it);
+			if (!ent->client->pers.inventory[index])
+			{
+				gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+				return;
+			}
+		}
+		// RAFAEL
+		else if (strcmp (it->pickup_name, "Railgun") == 0)
+		{
+			it = FindItem ("Phalanx");
+			index = ITEM_INDEX (it);
+			if (!ent->client->pers.inventory[index])
+			{
+				gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+				return;
+			}
+		}
+		else 
+		{
+			gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+			return;
+		}
+	}
+
+	it->use (ent, it);
+}
+
+
+/*
+==================
+Cmd_Drop_f
+
+Drop an inventory item
+==================
+*/
+void Cmd_Drop_f (edict_t *ent)
+{
+	int			index;
+	gitem_t		*it;
+	char		*s;
+
+	s = gi.args();
+	it = FindItem (s);
+	if (!it)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s);
+		return;
+	}
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+
+	index = ITEM_INDEX(it);
+	if (!ent->client->pers.inventory[index])
+	{
+		// RAFAEL
+		if (strcmp (it->pickup_name, "HyperBlaster") == 0)
+		{
+			it = FindItem ("Ionripper");
+			index = ITEM_INDEX (it);
+			if (!ent->client->pers.inventory[index])
+			{
+				gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+				return;
+			}
+		}
+		// RAFAEL
+		else if (strcmp (it->pickup_name, "Railgun") == 0)
+		{
+			it = FindItem ("Phalanx");
+			index = ITEM_INDEX (it);
+			if (!ent->client->pers.inventory[index])
+			{
+				gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+				return;
+			}
+		}
+		else 
+		{
+			gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s);
+			return;
+		}
+	}
+
+	it->drop (ent, it);
+}
+
+
+/*
+=================
+Cmd_Inven_f
+=================
+*/
+void Cmd_Inven_f (edict_t *ent)
+{
+	int			i;
+	gclient_t	*cl;
+
+	cl = ent->client;
+
+	cl->showscores = false;
+	cl->showhelp = false;
+
+	if (cl->showinventory)
+	{
+		cl->showinventory = false;
+		return;
+	}
+
+	cl->showinventory = true;
+
+	gi.WriteByte (svc_inventory);
+	for (i=0 ; i<MAX_ITEMS ; i++)
+	{
+		gi.WriteShort (cl->pers.inventory[i]);
+	}
+	gi.unicast (ent, true);
+}
+
+/*
+=================
+Cmd_InvUse_f
+=================
+*/
+void Cmd_InvUse_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to use.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->use)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n");
+		return;
+	}
+	it->use (ent, it);
+}
+
+/*
+=================
+Cmd_WeapPrev_f
+=================
+*/
+
+void Cmd_WeapPrev_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (selected_weapon + i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		if (cl->pers.weapon == it)
+			return;	// successful
+	}
+}
+
+/*
+=================
+Cmd_WeapNext_f
+=================
+*/
+/*
+void Cmd_WeapNext_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		if (cl->pers.weapon == it)
+			return;	// successful
+	}
+}
+*/
+void Cmd_WeapNext_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			i, index;
+	gitem_t		*it;
+	int			selected_weapon;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon)
+		return;
+
+	selected_weapon = ITEM_INDEX(cl->pers.weapon);
+
+	// scan  for the next valid one
+	for (i=1 ; i<=MAX_ITEMS ; i++)
+	{
+		index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS;
+		
+		if (!cl->pers.inventory[index])
+			continue;
+		it = &itemlist[index];
+		if (!it->use)
+			continue;
+		if (! (it->flags & IT_WEAPON) )
+			continue;
+		it->use (ent, it);
+		if (cl->pers.weapon == it)
+			return;	// successful
+	}
+}
+
+/*
+=================
+Cmd_WeapLast_f
+=================
+*/
+void Cmd_WeapLast_f (edict_t *ent)
+{
+	gclient_t	*cl;
+	int			index;
+	gitem_t		*it;
+
+	cl = ent->client;
+
+	if (!cl->pers.weapon || !cl->pers.lastweapon)
+		return;
+
+	index = ITEM_INDEX(cl->pers.lastweapon);
+	if (!cl->pers.inventory[index])
+		return;
+	it = &itemlist[index];
+	if (!it->use)
+		return;
+	if (! (it->flags & IT_WEAPON) )
+		return;
+	it->use (ent, it);
+}
+
+/*
+=================
+Cmd_InvDrop_f
+=================
+*/
+void Cmd_InvDrop_f (edict_t *ent)
+{
+	gitem_t		*it;
+
+	ValidateSelectedItem (ent);
+
+	if (ent->client->pers.selected_item == -1)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n");
+		return;
+	}
+
+	it = &itemlist[ent->client->pers.selected_item];
+	if (!it->drop)
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n");
+		return;
+	}
+	it->drop (ent, it);
+}
+
+/*
+=================
+Cmd_Kill_f
+=================
+*/
+void Cmd_Kill_f (edict_t *ent)
+{
+	if((level.time - ent->client->respawn_time) < 5)
+		return;
+	ent->flags &= ~FL_GODMODE;
+	ent->health = 0;
+	meansOfDeath = MOD_SUICIDE;
+	player_die (ent, ent, ent, 100000, vec3_origin);
+}
+
+/*
+=================
+Cmd_PutAway_f
+=================
+*/
+void Cmd_PutAway_f (edict_t *ent)
+{
+	ent->client->showscores = false;
+	ent->client->showhelp = false;
+	ent->client->showinventory = false;
+}
+
+
+int PlayerSort (void const *a, void const *b)
+{
+	int		anum, bnum;
+
+	anum = *(int *)a;
+	bnum = *(int *)b;
+
+	anum = game.clients[anum].ps.stats[STAT_FRAGS];
+	bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
+
+	if (anum < bnum)
+		return -1;
+	if (anum > bnum)
+		return 1;
+	return 0;
+}
+
+/*
+=================
+Cmd_Players_f
+=================
+*/
+void Cmd_Players_f (edict_t *ent)
+{
+	int		i;
+	int		count;
+	char	small[64];
+	char	large[1280];
+	int		index[256];
+
+	count = 0;
+	for (i = 0 ; i < maxclients->value ; i++)
+		if (game.clients[i].pers.connected)
+		{
+			index[count] = i;
+			count++;
+		}
+
+	// sort by frags
+	qsort (index, count, sizeof(index[0]), PlayerSort);
+
+	// print information
+	large[0] = 0;
+
+	for (i = 0 ; i < count ; i++)
+	{
+		Com_sprintf (small, sizeof(small), "%3i %s\n",
+			game.clients[index[i]].ps.stats[STAT_FRAGS],
+			game.clients[index[i]].pers.netname);
+		if (strlen (small) + strlen(large) > sizeof(large) - 100 )
+		{	// can't print all of them in one packet
+			strcat (large, "...\n");
+			break;
+		}
+		strcat (large, small);
+	}
+
+	gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count);
+}
+
+/*
+=================
+Cmd_Wave_f
+=================
+*/
+void Cmd_Wave_f (edict_t *ent)
+{
+	int		i;
+
+	i = atoi (gi.argv(1));
+
+	// can't wave when ducked
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+		return;
+
+	if (ent->client->anim_priority > ANIM_WAVE)
+		return;
+
+	ent->client->anim_priority = ANIM_WAVE;
+
+	switch (i)
+	{
+	case 0:
+		gi.cprintf (ent, PRINT_HIGH, "flipoff\n");
+		ent->s.frame = FRAME_flip01-1;
+		ent->client->anim_end = FRAME_flip12;
+		break;
+	case 1:
+		gi.cprintf (ent, PRINT_HIGH, "salute\n");
+		ent->s.frame = FRAME_salute01-1;
+		ent->client->anim_end = FRAME_salute11;
+		break;
+	case 2:
+		gi.cprintf (ent, PRINT_HIGH, "taunt\n");
+		ent->s.frame = FRAME_taunt01-1;
+		ent->client->anim_end = FRAME_taunt17;
+		break;
+	case 3:
+		gi.cprintf (ent, PRINT_HIGH, "wave\n");
+		ent->s.frame = FRAME_wave01-1;
+		ent->client->anim_end = FRAME_wave11;
+		break;
+	case 4:
+	default:
+		gi.cprintf (ent, PRINT_HIGH, "point\n");
+		ent->s.frame = FRAME_point01-1;
+		ent->client->anim_end = FRAME_point12;
+		break;
+	}
+}
+
+/*
+==================
+Cmd_Say_f
+==================
+*/
+void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0)
+{
+	int		i, j;
+	edict_t	*other;
+	char	*p;
+	char	text[2048];
+	gclient_t *cl;
+
+	if (gi.argc () < 2 && !arg0)
+		return;
+
+	if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
+		team = false;
+
+	if (team)
+		Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname);
+	else
+		Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname);
+
+	if (arg0)
+	{
+		strcat (text, gi.argv(0));
+		strcat (text, " ");
+		strcat (text, gi.args());
+	}
+	else
+	{
+		p = gi.args();
+
+		if (*p == '"')
+		{
+			p++;
+			p[strlen(p)-1] = 0;
+		}
+		strcat(text, p);
+	}
+
+	// don't let text be too long for malicious reasons
+	if (strlen(text) > 150)
+		text[150] = 0;
+
+	strcat(text, "\n");
+
+	if (flood_msgs->value) {
+		cl = ent->client;
+
+        if (level.time < cl->flood_locktill) {
+			gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n",
+				(int)(cl->flood_locktill - level.time));
+            return;
+        }
+        i = cl->flood_whenhead - flood_msgs->value + 1;
+        if (i < 0)
+            i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i;
+		if (cl->flood_when[i] && 
+			level.time - cl->flood_when[i] < flood_persecond->value) {
+			cl->flood_locktill = level.time + flood_waitdelay->value;
+			gi.cprintf(ent, PRINT_CHAT, "Flood protection:  You can't talk for %d seconds.\n",
+				(int)flood_waitdelay->value);
+            return;
+        }
+		cl->flood_whenhead = (cl->flood_whenhead + 1) %
+			(sizeof(cl->flood_when)/sizeof(cl->flood_when[0]));
+		cl->flood_when[cl->flood_whenhead] = level.time;
+	}
+
+	if (dedicated->value)
+		gi.cprintf(NULL, PRINT_CHAT, "%s", text);
+
+	for (j = 1; j <= game.maxclients; j++)
+	{
+		other = &g_edicts[j];
+		if (!other->inuse)
+			continue;
+		if (!other->client)
+			continue;
+		if (team)
+		{
+			if (!OnSameTeam(ent, other))
+				continue;
+		}
+		gi.cprintf(other, PRINT_CHAT, "%s", text);
+	}
+}
+
+void Cmd_PlayerList_f(edict_t *ent)
+{
+	int i;
+	char st[80];
+	char text[1400];
+	edict_t *e2;
+
+	// connect time, ping, score, name
+	*text = 0;
+	for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) {
+		if (!e2->inuse)
+			continue;
+
+		Com_sprintf(st, sizeof(st), "%02d:%02d %4d %3d %s%s\n",
+			(level.framenum - e2->client->resp.enterframe) / 600,
+			((level.framenum - e2->client->resp.enterframe) % 600)/10,
+			e2->client->ping,
+			e2->client->resp.score,
+			e2->client->pers.netname,
+			e2->client->pers.spectator ? " (spectator)" : "");
+		if (strlen(text) + strlen(st) > sizeof(text) - 50) {
+			sprintf(text+strlen(text), "And more...\n");
+			gi.cprintf(ent, PRINT_HIGH, "%s", text);
+			return;
+		}
+		strcat(text, st);
+	}
+	gi.cprintf(ent, PRINT_HIGH, "%s", text);
+}
+
+
+/*
+=================
+ClientCommand
+=================
+*/
+void ClientCommand (edict_t *ent)
+{
+	char	*cmd;
+
+	if (!ent->client)
+		return;		// not fully in game yet
+
+	cmd = gi.argv(0);
+
+	if (cistrcmp (cmd, "players") == 0)
+	{
+		Cmd_Players_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "say") == 0)
+	{
+		Cmd_Say_f (ent, false, false);
+		return;
+	}
+	if (cistrcmp (cmd, "say_team") == 0)
+	{
+		Cmd_Say_f (ent, true, false);
+		return;
+	}
+	if (cistrcmp (cmd, "score") == 0)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+	if (cistrcmp (cmd, "help") == 0)
+	{
+		Cmd_Help_f (ent);
+		return;
+	}
+
+	if (level.intermissiontime)
+		return;
+
+	if (cistrcmp (cmd, "use") == 0)
+		Cmd_Use_f (ent);
+	else if (cistrcmp (cmd, "drop") == 0)
+		Cmd_Drop_f (ent);
+	else if (cistrcmp (cmd, "give") == 0)
+		Cmd_Give_f (ent);
+	else if (cistrcmp (cmd, "god") == 0)
+		Cmd_God_f (ent);
+	else if (cistrcmp (cmd, "notarget") == 0)
+		Cmd_Notarget_f (ent);
+	else if (cistrcmp (cmd, "noclip") == 0)
+		Cmd_Noclip_f (ent);
+	else if (cistrcmp (cmd, "inven") == 0)
+		Cmd_Inven_f (ent);
+	else if (cistrcmp (cmd, "invnext") == 0)
+		SelectNextItem (ent, -1);
+	else if (cistrcmp (cmd, "invprev") == 0)
+		SelectPrevItem (ent, -1);
+	else if (cistrcmp (cmd, "invnextw") == 0)
+		SelectNextItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invprevw") == 0)
+		SelectPrevItem (ent, IT_WEAPON);
+	else if (cistrcmp (cmd, "invnextp") == 0)
+		SelectNextItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invprevp") == 0)
+		SelectPrevItem (ent, IT_POWERUP);
+	else if (cistrcmp (cmd, "invuse") == 0)
+		Cmd_InvUse_f (ent);
+	else if (cistrcmp (cmd, "invdrop") == 0)
+		Cmd_InvDrop_f (ent);
+	else if (cistrcmp (cmd, "weapprev") == 0)
+		Cmd_WeapPrev_f (ent);
+	else if (cistrcmp (cmd, "weapnext") == 0)
+		Cmd_WeapNext_f (ent);
+	else if (cistrcmp (cmd, "weaplast") == 0)
+		Cmd_WeapLast_f (ent);
+	else if (cistrcmp (cmd, "kill") == 0)
+		Cmd_Kill_f (ent);
+	else if (cistrcmp (cmd, "putaway") == 0)
+		Cmd_PutAway_f (ent);
+	else if (cistrcmp (cmd, "wave") == 0)
+		Cmd_Wave_f (ent);
+	else if (cistrcmp(cmd, "playerlist") == 0)
+		Cmd_PlayerList_f(ent);
+	else	// anything that doesn't match a command will be a chat
+		Cmd_Say_f (ent, false, true);
+}
--- /dev/null
+++ b/xatrix/g_combat.c
@@ -1,0 +1,562 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+============
+CanDamage
+
+Returns true if the inflictor can directly damage the target.  Used for
+explosions and melee attacks.
+============
+*/
+qboolean CanDamage (edict_t *targ, edict_t *inflictor)
+{
+	vec3_t	dest;
+	trace_t	trace;
+
+// bmodels need special checking because their origin is 0,0,0
+	if (targ->movetype == MOVETYPE_PUSH)
+	{
+		VectorAdd (targ->absmin, targ->absmax, dest);
+		VectorScale (dest, 0.5, dest);
+		trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+		if (trace.fraction == 1.0)
+			return true;
+		if (trace.ent == targ)
+			return true;
+		return false;
+	}
+	
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] += 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] += 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+	VectorCopy (targ->s.origin, dest);
+	dest[0] -= 15.0;
+	dest[1] -= 15.0;
+	trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
+	if (trace.fraction == 1.0)
+		return true;
+
+
+	return false;
+}
+
+
+/*
+============
+Killed
+============
+*/
+void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
+{
+	if (targ->health < -999)
+		targ->health = -999;
+
+	targ->enemy = attacker;
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+//		targ->svflags |= SVF_DEADMONSTER;	// now treat as a different content type
+		if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY))
+		{
+			level.killed_monsters++;
+			if (coop->value && attacker->client)
+				attacker->client->resp.score++;
+			// medics won't heal monsters that they kill themselves
+			if (strcmp(attacker->classname, "monster_medic") == 0)
+				targ->owner = attacker;
+		}
+	}
+
+	if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
+	{	// doors, triggers, etc
+		targ->die (targ, inflictor, attacker, damage, point);
+		return;
+	}
+
+	if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
+	{
+		targ->touch = NULL;
+		monster_death_use (targ);
+	}
+
+	targ->die (targ, inflictor, attacker, damage, point);
+}
+
+
+/*
+================
+SpawnDamage
+================
+*/
+void SpawnDamage (int type, vec3_t origin, vec3_t normal, int /*damage*/)
+{
+	//if (damage > 255)
+	//	damage = 255;
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (type);
+//	gi.WriteByte (damage);
+	gi.WritePosition (origin);
+	gi.WriteDir (normal);
+	gi.multicast (origin, MULTICAST_PVS);
+}
+
+
+/*
+============
+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
+point		point at which the damage is being inflicted
+normal		normal vector from that point
+damage		amount of damage being inflicted
+knockback	force to be applied against targ as a result of the damage
+
+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_ENERGY			damage is from an energy based weapon
+	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
+	DAMAGE_BULLET			damage is from a bullet (used for ricochets)
+	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
+============
+*/
+static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			power_armor_type;
+	int			index = 0;
+	int			damagePerCell;
+	int			pa_te_type;
+	int			power = 0;
+	int			power_used;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (dflags & DAMAGE_NO_ARMOR)
+		return 0;
+
+	if (client)
+	{
+		power_armor_type = PowerArmorType (ent);
+		if (power_armor_type != POWER_ARMOR_NONE)
+		{
+			index = ITEM_INDEX(FindItem("Cells"));
+			power = client->pers.inventory[index];
+		}
+	}
+	else if (ent->svflags & SVF_MONSTER)
+	{
+		power_armor_type = ent->monsterinfo.power_armor_type;
+		power = ent->monsterinfo.power_armor_power;
+	}
+	else
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_NONE)
+		return 0;
+	if (!power)
+		return 0;
+
+	if (power_armor_type == POWER_ARMOR_SCREEN)
+	{
+		vec3_t		vec;
+		float		dot;
+		vec3_t		forward;
+
+		// only works if damage point is in front
+		AngleVectors (ent->s.angles, forward, NULL, NULL);
+		VectorSubtract (point, ent->s.origin, vec);
+		VectorNormalize (vec);
+		dot = DotProduct (vec, forward);
+		if (dot <= 0.3)
+			return 0;
+
+		damagePerCell = 1;
+		pa_te_type = TE_SCREEN_SPARKS;
+		damage = damage / 3;
+	}
+	else
+	{
+		damagePerCell = 2;
+		pa_te_type = TE_SHIELD_SPARKS;
+		damage = (2 * damage) / 3;
+	}
+
+	save = power * damagePerCell;
+	if (!save)
+		return 0;
+	if (save > damage)
+		save = damage;
+
+	SpawnDamage (pa_te_type, point, normal, save);
+	ent->powerarmor_time = level.time + 0.2;
+
+	power_used = save / damagePerCell;
+
+	if (client)
+		client->pers.inventory[index] -= power_used;
+	else
+		ent->monsterinfo.power_armor_power -= power_used;
+	return save;
+}
+
+static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags)
+{
+	gclient_t	*client;
+	int			save;
+	int			index;
+	gitem_t		*armor;
+
+	if (!damage)
+		return 0;
+
+	client = ent->client;
+
+	if (!client)
+		return 0;
+
+	if (dflags & DAMAGE_NO_ARMOR)
+		return 0;
+
+	index = ArmorIndex (ent);
+	if (!index)
+		return 0;
+
+	armor = GetItemByIndex (index);
+
+	if (dflags & DAMAGE_ENERGY)
+		save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage);
+	else
+		save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage);
+	if (save >= client->pers.inventory[index])
+		save = client->pers.inventory[index];
+
+	if (!save)
+		return 0;
+
+	client->pers.inventory[index] -= save;
+	SpawnDamage (te_sparks, point, normal, save);
+
+	return save;
+}
+
+void M_ReactToDamage (edict_t *targ, edict_t *attacker)
+{
+	if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
+		return;
+
+	if (attacker == targ || attacker == targ->enemy)
+		return;
+
+	// if we are a good guy monster and our attacker is a player
+	// or another good guy, do not get mad at them
+	if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
+	{
+		if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
+			return;
+	}
+
+	// we now know that we are not both good guys
+
+	// if attacker is a client, get mad at them because he's good and we're not
+	if (attacker->client)
+	{
+		targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
+
+		// this can only happen in coop (both new and old enemies are clients)
+		// only switch if can't see the current enemy
+		if (targ->enemy && targ->enemy->client)
+		{
+			if (visible(targ, targ->enemy))
+			{
+				targ->oldenemy = attacker;
+				return;
+			}
+			targ->oldenemy = targ->enemy;
+		}
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+		return;
+	}
+
+	// it's the same base (walk/swim/fly) type and a different classname and it's not a tank
+	// (they spray too much), get mad at them
+	if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) &&
+		 (strcmp (targ->classname, attacker->classname) != 0) &&
+		 (strcmp(attacker->classname, "monster_tank") != 0) &&
+		 (strcmp(attacker->classname, "monster_supertank") != 0) &&
+		 (strcmp(attacker->classname, "monster_makron") != 0) &&
+		 (strcmp(attacker->classname, "monster_jorg") != 0) )
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+	// if they *meant* to shoot us, then shoot back
+	else if (attacker->enemy == targ)
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+	// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
+	else if (attacker->enemy && attacker->enemy != targ)
+	{
+		if (targ->enemy && targ->enemy->client)
+			targ->oldenemy = targ->enemy;
+		targ->enemy = attacker->enemy;
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED))
+			FoundTarget (targ);
+	}
+}
+
+qboolean CheckTeamDamage (edict_t *, edict_t *)
+{
+		//FIXME make the next line real and uncomment this block
+		// if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
+	return false;
+}
+
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
+{
+	gclient_t	*client;
+	int			take;
+	int			save;
+	int			asave;
+	int			psave;
+	int			te_sparks;
+
+	if (!targ->takedamage)
+		return;
+
+	// friendly fire avoidance
+	// if enabled you can't hurt teammates (but you can hurt yourself)
+	// knockback still occurs
+	if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
+	{
+		if (OnSameTeam (targ, attacker))
+		{
+			if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
+				damage = 0;
+			else
+				mod |= MOD_FRIENDLY_FIRE;
+		}
+	}
+	meansOfDeath = mod;
+
+	// easy mode takes half damage
+	if (skill->value == 0 && deathmatch->value == 0 && targ->client)
+	{
+		damage *= 0.5;
+		if (!damage)
+			damage = 1;
+	}
+
+	client = targ->client;
+
+	if (dflags & DAMAGE_BULLET)
+		te_sparks = TE_BULLET_SPARKS;
+	else
+		te_sparks = TE_SPARKS;
+
+	VectorNormalize(dir);
+
+// bonus damage for suprising a monster
+	if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
+		damage *= 2;
+
+	if (targ->flags & FL_NO_KNOCKBACK)
+		knockback = 0;
+
+// figure momentum add
+	if (!(dflags & DAMAGE_NO_KNOCKBACK))
+	{
+		if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
+		{
+			vec3_t	kvel;
+			float	mass;
+
+			if (targ->mass < 50)
+				mass = 50;
+			else
+				mass = targ->mass;
+
+			if (targ->client  && attacker == targ)
+				VectorScale (dir, 1600.0 * (float)knockback / mass, kvel);	// the rocket jump hack...
+			else
+				VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
+
+			VectorAdd (targ->velocity, kvel, targ->velocity);
+		}
+	}
+
+	take = damage;
+	save = 0;
+
+	// check for godmode
+	if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
+	{
+		take = 0;
+		save = damage;
+		SpawnDamage (te_sparks, point, normal, save);
+	}
+
+	// check for invincibility
+	if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION) && mod != MOD_TRAP)
+	{
+		if (targ->pain_debounce_time < level.time)
+		{
+			gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
+			targ->pain_debounce_time = level.time + 2;
+		}
+		take = 0;
+		save = damage;
+	}
+
+	psave = CheckPowerArmor (targ, point, normal, take, dflags);
+	take -= psave;
+
+	asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
+	take -= asave;
+
+	//treat cheat/powerup savings the same as armor
+	asave += save;
+
+	// team damage avoidance
+	if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
+		return;
+
+// do the damage
+	if (take)
+	{
+		if ((targ->svflags & SVF_MONSTER) || (client))
+			if (strcmp (targ->classname, "monster_gekk") == 0)
+				SpawnDamage (TE_GREENBLOOD, point, normal, take);
+			else
+				SpawnDamage (TE_BLOOD, point, normal, take);
+		else
+			SpawnDamage (te_sparks, point, normal, take);
+
+
+		targ->health = targ->health - take;
+			
+		if (targ->health <= 0)
+		{
+			if ((targ->svflags & SVF_MONSTER) || (client))
+				targ->flags |= FL_NO_KNOCKBACK;
+			Killed (targ, inflictor, attacker, take, point);
+			return;
+		}
+	}
+
+	if (targ->svflags & SVF_MONSTER)
+	{
+		M_ReactToDamage (targ, attacker);
+		if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take))
+		{
+			targ->pain (targ, attacker, knockback, take);
+			// nightmare mode monsters don't go into pain frames often
+			if (skill->value == 3)
+				targ->pain_debounce_time = level.time + 5;
+		}
+	}
+	else if (client)
+	{
+		if (!(targ->flags & FL_GODMODE) && (take))
+			targ->pain (targ, attacker, knockback, take);
+	}
+	else if (take)
+	{
+		if (targ->pain)
+			targ->pain (targ, attacker, knockback, take);
+	}
+
+	// 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)
+	{
+		client->damage_parmor += psave;
+		client->damage_armor += asave;
+		client->damage_blood += take;
+		client->damage_knockback += knockback;
+		VectorCopy (point, client->damage_from);
+	}
+}
+
+
+/*
+============
+T_RadiusDamage
+============
+*/
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod)
+{
+	float	points;
+	edict_t	*ent = NULL;
+	vec3_t	v;
+	vec3_t	dir;
+
+	while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL)
+	{
+		if (ent == ignore)
+			continue;
+		if (!ent->takedamage)
+			continue;
+
+		VectorAdd (ent->mins, ent->maxs, v);
+		VectorMA (ent->s.origin, 0.5, v, v);
+		VectorSubtract (inflictor->s.origin, v, v);
+		points = damage - 0.5 * VectorLength (v);
+		if (ent == attacker)
+			points = points * 0.5;
+		if (points > 0)
+		{
+			if (CanDamage (ent, inflictor))
+			{
+				VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
+				T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+			}
+		}
+	}
+}
--- /dev/null
+++ b/xatrix/g_func.c
@@ -1,0 +1,2220 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+=========================================================
+
+  PLATS
+
+  movement options:
+
+  linear
+  smooth start, hard stop
+  smooth start, smooth stop
+
+  start
+  end
+  acceleration
+  speed
+  deceleration
+  begin sound
+  end sound
+  target fired when reaching end
+  wait at end
+
+  object characteristics that use move segments
+  ---------------------------------------------
+  movetype_push, or movetype_stop
+  action when touched
+  action when blocked
+  action when used
+	disabled?
+  auto trigger spawning
+
+
+=========================================================
+*/
+
+#define PLAT_LOW_TRIGGER	1
+
+#define	STATE_TOP			0
+#define	STATE_BOTTOM		1
+#define STATE_UP			2
+#define STATE_DOWN			3
+
+#define DOOR_START_OPEN		1
+#define DOOR_REVERSE		2
+#define DOOR_CRUSHER		4
+#define DOOR_NOMONSTER		8
+#define DOOR_TOGGLE			32
+#define DOOR_X_AXIS			64
+#define DOOR_Y_AXIS			128
+
+
+//
+// Support routines for movement (changes in origin using velocity)
+//
+
+void Move_Done (edict_t *ent)
+{
+	VectorClear (ent->velocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void Move_Final (edict_t *ent)
+{
+	if (ent->moveinfo.remaining_distance == 0)
+	{
+		Move_Done (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity);
+
+	ent->think = Move_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void Move_Begin (edict_t *ent)
+{
+	float	frames;
+
+	if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance)
+	{
+		Move_Final (ent);
+		return;
+	}
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity);
+	frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME);
+	ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME;
+	ent->nextthink = level.time + (frames * FRAMETIME);
+	ent->think = Move_Final;
+}
+
+void Think_AccelMove (edict_t *ent);
+
+void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*))
+{
+	VectorClear (ent->velocity);
+	VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir);
+	ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir);
+	ent->moveinfo.endfunc = func;
+
+	if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
+	{
+		if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+		{
+			Move_Begin (ent);
+		}
+		else
+		{
+			ent->nextthink = level.time + FRAMETIME;
+			ent->think = Move_Begin;
+		}
+	}
+	else
+	{
+		// accelerative
+		ent->moveinfo.current_speed = 0;
+		ent->think = Think_AccelMove;
+		ent->nextthink = level.time + FRAMETIME;
+	}
+}
+
+
+//
+// Support routines for angular movement (changes in angle using avelocity)
+//
+
+void AngleMove_Done (edict_t *ent)
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc (ent);
+}
+
+void AngleMove_Final (edict_t *ent)
+{
+	vec3_t	move;
+
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move);
+
+	if (VectorCompare (move, vec3_origin))
+	{
+		AngleMove_Done (ent);
+		return;
+	}
+
+	VectorScale (move, 1.0/FRAMETIME, ent->avelocity);
+
+	ent->think = AngleMove_Done;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void AngleMove_Begin (edict_t *ent)
+{
+	vec3_t	destdelta;
+	float	len;
+	float	traveltime;
+	float	frames;
+
+	// set destdelta to the vector needed to move
+	if (ent->moveinfo.state == STATE_UP)
+		VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta);
+	else
+		VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta);
+	
+	// calculate length of vector
+	len = VectorLength (destdelta);
+	
+	// divide by speed to get time to reach dest
+	traveltime = len / ent->moveinfo.speed;
+
+	if (traveltime < FRAMETIME)
+	{
+		AngleMove_Final (ent);
+		return;
+	}
+
+	frames = floor(traveltime / FRAMETIME);
+
+	// scale the destdelta vector by the time spent traveling to get velocity
+	VectorScale (destdelta, 1.0 / traveltime, ent->avelocity);
+
+	// set nextthink to trigger a think when dest is reached
+	ent->nextthink = level.time + frames * FRAMETIME;
+	ent->think = AngleMove_Final;
+}
+
+void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*))
+{
+	VectorClear (ent->avelocity);
+	ent->moveinfo.endfunc = func;
+	if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
+	{
+		AngleMove_Begin (ent);
+	}
+	else
+	{
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = AngleMove_Begin;
+	}
+}
+
+
+/*
+==============
+Think_AccelMove
+
+The team has completed a frame of movement, so
+change the speed for the next frame
+==============
+*/
+#define AccelerationDistance(target, rate)	(target * ((target / rate) + 1) / 2)
+
+void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
+{
+	float	accel_dist;
+	float	decel_dist;
+
+	moveinfo->move_speed = moveinfo->speed;
+
+	if (moveinfo->remaining_distance < moveinfo->accel)
+	{
+		moveinfo->current_speed = moveinfo->remaining_distance;
+		return;
+	}
+
+	accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel);
+	decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel);
+
+	if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0)
+	{
+		float	f;
+
+		f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel);
+		moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f);
+		decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel);
+	}
+
+	moveinfo->decel_distance = decel_dist;
+};
+
+void plat_Accelerate (moveinfo_t *moveinfo)
+{
+	// are we decelerating?
+	if (moveinfo->remaining_distance <= moveinfo->decel_distance)
+	{
+		if (moveinfo->remaining_distance < moveinfo->decel_distance)
+		{
+			if (moveinfo->next_speed)
+			{
+				moveinfo->current_speed = moveinfo->next_speed;
+				moveinfo->next_speed = 0;
+				return;
+			}
+			if (moveinfo->current_speed > moveinfo->decel)
+				moveinfo->current_speed -= moveinfo->decel;
+		}
+		return;
+	}
+
+	// are we at full speed and need to start decelerating during this move?
+	if (moveinfo->current_speed == moveinfo->move_speed)
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance)
+		{
+			float	p1_distance;
+			float	p2_distance;
+			float	distance;
+
+			p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+			p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed));
+			distance = p1_distance + p2_distance;
+			moveinfo->current_speed = moveinfo->move_speed;
+			moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+			return;
+		}
+
+	// are we accelerating?
+	if (moveinfo->current_speed < moveinfo->speed)
+	{
+		float	old_speed;
+		float	p1_distance;
+		float	p1_speed;
+		float	p2_distance;
+		float	distance;
+
+		old_speed = moveinfo->current_speed;
+
+		// figure simple acceleration up to move_speed
+		moveinfo->current_speed += moveinfo->accel;
+		if (moveinfo->current_speed > moveinfo->speed)
+			moveinfo->current_speed = moveinfo->speed;
+
+		// are we accelerating throughout this entire move?
+		if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance)
+			return;
+
+		// during this move we will accelrate from current_speed to move_speed
+		// and cross over the decel_distance; figure the average speed for the
+		// entire move
+		p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance;
+		p1_speed = (old_speed + moveinfo->move_speed) / 2.0;
+		p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed));
+		distance = p1_distance + p2_distance;
+		moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance));
+		moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance);
+		return;
+	}
+
+	// we are at constant velocity (move_speed)
+	return;
+};
+
+void Think_AccelMove (edict_t *ent)
+{
+	ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
+
+	if (ent->moveinfo.current_speed == 0)		// starting or blocked
+		plat_CalcAcceleratedMove(&ent->moveinfo);
+
+	plat_Accelerate (&ent->moveinfo);
+
+	// will the entire move complete on next frame?
+	if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
+	{
+		Move_Final (ent);
+		return;
+	}
+
+	VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity);
+	ent->nextthink = level.time + FRAMETIME;
+	ent->think = Think_AccelMove;
+}
+
+
+void plat_go_down (edict_t *ent);
+
+void plat_hit_top (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_TOP;
+
+	ent->think = plat_go_down;
+	ent->nextthink = level.time + 3;
+}
+
+void plat_hit_bottom (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_end)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		ent->s.sound = 0;
+	}
+	ent->moveinfo.state = STATE_BOTTOM;
+}
+
+void plat_go_down (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_DOWN;
+	Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom);
+}
+
+void plat_go_up (edict_t *ent)
+{
+	if (!(ent->flags & FL_TEAMSLAVE))
+	{
+		if (ent->moveinfo.sound_start)
+			gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		ent->s.sound = ent->moveinfo.sound_middle;
+	}
+	ent->moveinfo.state = STATE_UP;
+	Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top);
+}
+
+void plat_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->moveinfo.state == STATE_UP)
+		plat_go_down (self);
+	else if (self->moveinfo.state == STATE_DOWN)
+		plat_go_up (self);
+}
+
+
+void Use_Plat (edict_t *ent, edict_t *, edict_t *)
+{ 
+	if (ent->think)
+		return;		// already down
+	plat_go_down (ent);
+}
+
+
+void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+		
+	if (other->health <= 0)
+		return;
+
+	ent = ent->enemy;	// now point at the plat, not the trigger
+	if (ent->moveinfo.state == STATE_BOTTOM)
+		plat_go_up (ent);
+	else if (ent->moveinfo.state == STATE_TOP)
+		ent->nextthink = level.time + 1;	// the player is still on the plat, so delay going down
+}
+
+void plat_spawn_inside_trigger (edict_t *ent)
+{
+	edict_t	*trigger;
+	vec3_t	tmin, tmax;
+
+//
+// middle trigger
+//	
+	trigger = G_Spawn();
+	trigger->touch = Touch_Plat_Center;
+	trigger->movetype = MOVETYPE_NONE;
+	trigger->solid = SOLID_TRIGGER;
+	trigger->enemy = ent;
+	
+	tmin[0] = ent->mins[0] + 25;
+	tmin[1] = ent->mins[1] + 25;
+	tmin[2] = ent->mins[2];
+
+	tmax[0] = ent->maxs[0] - 25;
+	tmax[1] = ent->maxs[1] - 25;
+	tmax[2] = ent->maxs[2] + 8;
+
+	tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip);
+
+	if (ent->spawnflags & PLAT_LOW_TRIGGER)
+		tmax[2] = tmin[2] + 8;
+	
+	if (tmax[0] - tmin[0] <= 0)
+	{
+		tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5;
+		tmax[0] = tmin[0] + 1;
+	}
+	if (tmax[1] - tmin[1] <= 0)
+	{
+		tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5;
+		tmax[1] = tmin[1] + 1;
+	}
+	
+	VectorCopy (tmin, trigger->mins);
+	VectorCopy (tmax, trigger->maxs);
+
+	gi.linkentity (trigger);
+}
+
+
+/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
+speed	default 150
+
+Plats are always drawn in the extended position, so they will light correctly.
+
+If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
+
+"speed"	overrides default 200.
+"accel" overrides default 500
+"lip"	overrides default 8 pixel lip
+
+If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
+
+Set "sounds" to one of the following:
+1) base fast
+2) chain slow
+*/
+void SP_func_plat (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+	ent->solid = SOLID_BSP;
+	ent->movetype = MOVETYPE_PUSH;
+
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = plat_blocked;
+
+	if (!ent->speed)
+		ent->speed = 20;
+	else
+		ent->speed *= 0.1;
+
+	if (!ent->accel)
+		ent->accel = 5;
+	else
+		ent->accel *= 0.1;
+
+	if (!ent->decel)
+		ent->decel = 5;
+	else
+		ent->decel *= 0.1;
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!st.lip)
+		st.lip = 8;
+
+	// pos1 is the top position, pos2 is the bottom
+	VectorCopy (ent->s.origin, ent->pos1);
+	VectorCopy (ent->s.origin, ent->pos2);
+	if (st.height)
+		ent->pos2[2] -= st.height;
+	else
+		ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
+
+	ent->use = Use_Plat;
+
+	plat_spawn_inside_trigger (ent);	// the "start moving" trigger	
+
+	if (ent->targetname)
+	{
+		ent->moveinfo.state = STATE_UP;
+	}
+	else
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		gi.linkentity (ent);
+		ent->moveinfo.state = STATE_BOTTOM;
+	}
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");
+}
+
+//====================================================================
+
+/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST
+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.
+
+"speed" determines how fast it moves; default value is 100.
+"dmg"	damage to inflict when blocked (2 default)
+
+REVERSE will cause the it to rotate in the opposite direction.
+STOP mean it will stop moving instead of pushing entities
+*/
+
+void rotating_blocked (edict_t *self, edict_t *other)
+{
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2])
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void rotating_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!VectorCompare (self->avelocity, vec3_origin))
+	{
+		self->s.sound = 0;
+		VectorClear (self->avelocity);
+		self->touch = NULL;
+	}
+	else
+	{
+		self->s.sound = self->moveinfo.sound_middle;
+		VectorScale (self->movedir, self->speed, self->avelocity);
+		if (self->spawnflags & 16)
+			self->touch = rotating_touch;
+	}
+}
+
+void SP_func_rotating (edict_t *ent)
+{
+	ent->solid = SOLID_BSP;
+	if (ent->spawnflags & 32)
+		ent->movetype = MOVETYPE_STOP;
+	else
+		ent->movetype = MOVETYPE_PUSH;
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & 4)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & 8)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & 2)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+//	ent->moveinfo.sound_middle = "doors/hydro1.wav";
+
+	ent->use = rotating_use;
+	if (ent->dmg)
+		ent->blocked = rotating_blocked;
+
+	if (ent->spawnflags & 1)
+		ent->use (ent, NULL, NULL);
+
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 128)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+BUTTONS
+
+======================================================================
+*/
+
+/*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.
+
+"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
+"sounds"
+1) silent
+2) steam metal
+3) wooden clunk
+4) metallic click
+5) in-out
+*/
+
+void button_done (edict_t *self)
+{
+	self->moveinfo.state = STATE_BOTTOM;
+	self->s.effects &= ~EF_ANIM23;
+	self->s.effects |= EF_ANIM01;
+}
+
+void button_return (edict_t *self)
+{
+	self->moveinfo.state = STATE_DOWN;
+
+	Move_Calc (self, self->moveinfo.start_origin, button_done);
+
+	self->s.frame = 0;
+
+	if (self->health)
+		self->takedamage = DAMAGE_YES;
+}
+
+void button_wait (edict_t *self)
+{
+	self->moveinfo.state = STATE_TOP;
+	self->s.effects &= ~EF_ANIM01;
+	self->s.effects |= EF_ANIM23;
+
+	G_UseTargets (self, self->activator);
+	self->s.frame = 1;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->nextthink = level.time + self->moveinfo.wait;
+		self->think = button_return;
+	}
+}
+
+void button_fire (edict_t *self)
+{
+	if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		return;
+
+	self->moveinfo.state = STATE_UP;
+	if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE))
+		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+	Move_Calc (self, self->moveinfo.end_origin, button_wait);
+}
+
+void button_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	button_fire (self);
+}
+
+void button_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (other->health <= 0)
+		return;
+
+	self->activator = other;
+	button_fire (self);
+}
+
+void button_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->activator = attacker;
+	self->health = self->max_health;
+	self->takedamage = DAMAGE_NO;
+	button_fire (self);
+}
+
+void SP_func_button (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+	float	dist;
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_STOP;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	if (ent->sounds != 1)
+		ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav");
+	
+	if (!ent->speed)
+		ent->speed = 40;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 4;
+
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, dist, ent->movedir, ent->pos2);
+
+	ent->use = button_use;
+	ent->s.effects |= EF_ANIM01;
+
+	if (ent->health)
+	{
+		ent->max_health = ent->health;
+		ent->die = button_killed;
+		ent->takedamage = DAMAGE_YES;
+	}
+	else if (! ent->targetname)
+		ent->touch = button_touch;
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	gi.linkentity (ent);
+}
+
+/*
+======================================================================
+
+DOORS
+
+  spawn a trigger surrounding the entire team unless it is
+  already targeted by another
+
+======================================================================
+*/
+
+/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST
+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
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"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)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void door_use_areaportals (edict_t *self, qboolean open)
+{
+	edict_t	*t = NULL;
+
+	if (!self->target)
+		return;
+
+	while ((t = G_Find (t, FOFS(targetname), self->target)))
+	{
+		if (cistrcmp(t->classname, "func_areaportal") == 0)
+		{
+			gi.SetAreaPortalState (t->style, open);
+		}
+	}
+}
+
+void door_go_down (edict_t *self);
+
+void door_hit_top (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_TOP;
+	if (self->spawnflags & DOOR_TOGGLE)
+		return;
+	if (self->moveinfo.wait >= 0)
+	{
+		self->think = door_go_down;
+		self->nextthink = level.time + self->moveinfo.wait;
+	}
+}
+
+void door_hit_bottom (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_end)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+		self->s.sound = 0;
+	}
+	self->moveinfo.state = STATE_BOTTOM;
+	door_use_areaportals (self, false);
+}
+
+void door_go_down (edict_t *self)
+{
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	if (self->max_health)
+	{
+		self->takedamage = DAMAGE_YES;
+		self->health = self->max_health;
+	}
+	
+	self->moveinfo.state = STATE_DOWN;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_bottom);
+}
+
+void door_go_up (edict_t *self, edict_t *activator)
+{
+	if (self->moveinfo.state == STATE_UP)
+		return;		// already going up
+
+	if (self->moveinfo.state == STATE_TOP)
+	{	// reset top wait time
+		if (self->moveinfo.wait >= 0)
+			self->nextthink = level.time + self->moveinfo.wait;
+		return;
+	}
+	
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+	self->moveinfo.state = STATE_UP;
+	if (strcmp(self->classname, "func_door") == 0)
+		Move_Calc (self, self->moveinfo.end_origin, door_hit_top);
+	else if (strcmp(self->classname, "func_door_rotating") == 0)
+		AngleMove_Calc (self, door_hit_top);
+
+	G_UseTargets (self, activator);
+	door_use_areaportals (self, true);
+}
+
+void door_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*ent;
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;
+
+	if (self->spawnflags & DOOR_TOGGLE)
+	{
+		if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP)
+		{
+			// trigger all paired doors
+			for (ent = self ; ent ; ent = ent->teamchain)
+			{
+				ent->message = NULL;
+				ent->touch = NULL;
+				door_go_down (ent);
+			}
+			return;
+		}
+	}
+	
+	// trigger all paired doors
+	for (ent = self ; ent ; ent = ent->teamchain)
+	{
+		ent->message = NULL;
+		ent->touch = NULL;
+		door_go_up (ent, activator);
+	}
+};
+
+void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->health <= 0)
+		return;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client))
+		return;
+
+	if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER))
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 1.0;
+
+	door_use (self->owner, other, other);
+}
+
+void Think_CalcMoveSpeed (edict_t *self)
+{
+	edict_t	*ent;
+	float	min;
+	float	time;
+	float	newspeed;
+	float	ratio;
+	float	dist;
+
+	if (self->flags & FL_TEAMSLAVE)
+		return;		// only the team master does this
+
+	// find the smallest distance any member of the team will be moving
+	min = fabs(self->moveinfo.distance);
+	for (ent = self->teamchain; ent; ent = ent->teamchain)
+	{
+		dist = fabs(ent->moveinfo.distance);
+		if (dist < min)
+			min = dist;
+	}
+
+	time = min / self->moveinfo.speed;
+
+	// adjust speeds so they will all complete at the same time
+	for (ent = self; ent; ent = ent->teamchain)
+	{
+		newspeed = fabs(ent->moveinfo.distance) / time;
+		ratio = newspeed / ent->moveinfo.speed;
+		if (ent->moveinfo.accel == ent->moveinfo.speed)
+			ent->moveinfo.accel = newspeed;
+		else
+			ent->moveinfo.accel *= ratio;
+		if (ent->moveinfo.decel == ent->moveinfo.speed)
+			ent->moveinfo.decel = newspeed;
+		else
+			ent->moveinfo.decel *= ratio;
+		ent->moveinfo.speed = newspeed;
+	}
+}
+
+void Think_SpawnDoorTrigger (edict_t *ent)
+{
+	edict_t		*other;
+	vec3_t		mins, maxs;
+
+	if (ent->flags & FL_TEAMSLAVE)
+		return;		// only the team leader spawns a trigger
+
+	VectorCopy (ent->absmin, mins);
+	VectorCopy (ent->absmax, maxs);
+
+	for (other = ent->teamchain ; other ; other=other->teamchain)
+	{
+		AddPointToBounds (other->absmin, mins, maxs);
+		AddPointToBounds (other->absmax, mins, maxs);
+	}
+
+	// expand 
+	mins[0] -= 60;
+	mins[1] -= 60;
+	maxs[0] += 60;
+	maxs[1] += 60;
+
+	other = G_Spawn ();
+	VectorCopy (mins, other->mins);
+	VectorCopy (maxs, other->maxs);
+	other->owner = ent;
+	other->solid = SOLID_TRIGGER;
+	other->movetype = MOVETYPE_NONE;
+	other->touch = Touch_DoorTrigger;
+	gi.linkentity (other);
+
+	if (ent->spawnflags & DOOR_START_OPEN)
+		door_use_areaportals (ent, true);
+
+	Think_CalcMoveSpeed (ent);
+}
+
+void door_blocked  (edict_t *self, edict_t *other)
+{
+	edict_t	*ent;
+
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+
+	if (self->spawnflags & DOOR_CRUSHER)
+		return;
+
+
+// if a door has a negative wait, it would never come back if blocked,
+// so let it just squash the object to death real fast
+	if (self->moveinfo.wait >= 0)
+	{
+		if (self->moveinfo.state == STATE_DOWN)
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_up (ent, ent->activator);
+		}
+		else
+		{
+			for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+				door_go_down (ent);
+		}
+	}
+}
+
+void door_killed (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	edict_t	*ent;
+
+	for (ent = self->teammaster ; ent ; ent = ent->teamchain)
+	{
+		ent->health = ent->max_health;
+		ent->takedamage = DAMAGE_NO;
+	}
+	door_use (self->teammaster, attacker, attacker);
+}
+
+void door_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (!other->client)
+		return;
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 5.0;
+
+	gi.centerprintf (other, "%s", self->message);
+	gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+}
+
+void SP_func_door (edict_t *ent)
+{
+	vec3_t	abs_movedir;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	G_SetMovedir (ent->s.angles, ent->movedir);
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+	
+	if (!ent->speed)
+		ent->speed = 100;
+	if (deathmatch->value)
+		ent->speed *= 2;
+
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!st.lip)
+		st.lip = 8;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	// calculate second position
+	VectorCopy (ent->s.origin, ent->pos1);
+	abs_movedir[0] = fabs(ent->movedir[0]);
+	abs_movedir[1] = fabs(ent->movedir[1]);
+	abs_movedir[2] = fabs(ent->movedir[2]);
+	ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip;
+	VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2);
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.origin);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.origin, ent->pos1);
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->pos1, ent->moveinfo.start_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.start_angles);
+	VectorCopy (ent->pos2, ent->moveinfo.end_origin);
+	VectorCopy (ent->s.angles, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+	if (ent->spawnflags & 64)
+		ent->s.effects |= EF_ANIM_ALLFAST;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+}
+
+
+/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS
+TOGGLE causes the door to 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
+
+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.
+
+"distance" is how many degrees the door will be rotated.
+"speed" determines how fast the door moves; default value is 100.
+
+REVERSE will cause the door to rotate in the opposite direction.
+
+"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle"		determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health"	if set, door must be shot open
+"speed"		movement speed (100 default)
+"wait"		wait before returning (3 default, -1 = never return)
+"dmg"		damage to inflict when blocked (2 default)
+"sounds"
+1)	silent
+2)	light
+3)	medium
+4)	heavy
+*/
+
+void SP_func_door_rotating (edict_t *ent)
+{
+	VectorClear (ent->s.angles);
+
+	// set the axis of rotation
+	VectorClear(ent->movedir);
+	if (ent->spawnflags & DOOR_X_AXIS)
+		ent->movedir[2] = 1.0;
+	else if (ent->spawnflags & DOOR_Y_AXIS)
+		ent->movedir[0] = 1.0;
+	else // Z_AXIS
+		ent->movedir[1] = 1.0;
+
+	// check for reverse rotation
+	if (ent->spawnflags & DOOR_REVERSE)
+		VectorNegate (ent->movedir, ent->movedir);
+
+	if (!st.distance)
+	{
+		gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin));
+		st.distance = 90;
+	}
+
+	VectorCopy (ent->s.angles, ent->pos1);
+	VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2);
+	ent->moveinfo.distance = st.distance;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_blocked;
+	ent->use = door_use;
+
+	if (!ent->speed)
+		ent->speed = 100;
+	if (!ent->accel)
+		ent->accel = ent->speed;
+	if (!ent->decel)
+		ent->decel = ent->speed;
+
+	if (!ent->wait)
+		ent->wait = 3;
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (ent->sounds != 1)
+	{
+		ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+		ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+		ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+	}
+
+	// if it starts open, switch the positions
+	if (ent->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (ent->pos2, ent->s.angles);
+		VectorCopy (ent->pos1, ent->pos2);
+		VectorCopy (ent->s.angles, ent->pos1);
+		VectorNegate (ent->movedir, ent->movedir);
+	}
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	
+	if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+
+	ent->moveinfo.state = STATE_BOTTOM;
+	ent->moveinfo.speed = ent->speed;
+	ent->moveinfo.accel = ent->accel;
+	ent->moveinfo.decel = ent->decel;
+	ent->moveinfo.wait = ent->wait;
+	VectorCopy (ent->s.origin, ent->moveinfo.start_origin);
+	VectorCopy (ent->pos1, ent->moveinfo.start_angles);
+	VectorCopy (ent->s.origin, ent->moveinfo.end_origin);
+	VectorCopy (ent->pos2, ent->moveinfo.end_angles);
+
+	if (ent->spawnflags & 16)
+		ent->s.effects |= EF_ANIM_ALL;
+
+	// to simplify logic elsewhere, make non-teamed doors into a team of one
+	if (!ent->team)
+		ent->teammaster = ent;
+
+	gi.linkentity (ent);
+
+	ent->nextthink = level.time + FRAMETIME;
+	if (ent->health || ent->targetname)
+		ent->think = Think_CalcMoveSpeed;
+	else
+		ent->think = Think_SpawnDoorTrigger;
+}
+
+
+/*QUAKED func_water (0 .5 .8) ? START_OPEN
+func_water is a moveable water brush.  It must be targeted to operate.  Use a non-water texture at your own risk.
+
+START_OPEN causes the water to move to its destination when spawned and operate in reverse.
+
+"angle"		determines the opening direction (up or down only)
+"speed"		movement speed (25 default)
+"wait"		wait before returning (-1 default, -1 = TOGGLE)
+"lip"		lip remaining at end of move (0 default)
+"sounds"	(yes, these need to be changed)
+0)	no sound
+1)	water
+2)	lava
+*/
+
+void SP_func_water (edict_t *self)
+{
+	vec3_t	abs_movedir;
+
+	G_SetMovedir (self->s.angles, self->movedir);
+	self->movetype = MOVETYPE_PUSH;
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	switch (self->sounds)
+	{
+		default:
+			break;
+
+		case 1: // water
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+
+		case 2: // lava
+			self->moveinfo.sound_start = gi.soundindex  ("world/mov_watr.wav");
+			self->moveinfo.sound_end = gi.soundindex  ("world/stp_watr.wav");
+			break;
+	}
+
+	// calculate second position
+	VectorCopy (self->s.origin, self->pos1);
+	abs_movedir[0] = fabs(self->movedir[0]);
+	abs_movedir[1] = fabs(self->movedir[1]);
+	abs_movedir[2] = fabs(self->movedir[2]);
+	self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip;
+	VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2);
+
+	// if it starts open, switch the positions
+	if (self->spawnflags & DOOR_START_OPEN)
+	{
+		VectorCopy (self->pos2, self->s.origin);
+		VectorCopy (self->pos1, self->pos2);
+		VectorCopy (self->s.origin, self->pos1);
+	}
+
+	VectorCopy (self->pos1, self->moveinfo.start_origin);
+	VectorCopy (self->s.angles, self->moveinfo.start_angles);
+	VectorCopy (self->pos2, self->moveinfo.end_origin);
+	VectorCopy (self->s.angles, self->moveinfo.end_angles);
+
+	self->moveinfo.state = STATE_BOTTOM;
+
+	if (!self->speed)
+		self->speed = 25;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed;
+
+	if (!self->wait)
+		self->wait = -1;
+	self->moveinfo.wait = self->wait;
+
+	self->use = door_use;
+
+	if (self->wait == -1)
+		self->spawnflags |= DOOR_TOGGLE;
+
+	self->classname = "func_door";
+
+	gi.linkentity (self);
+}
+
+
+#define TRAIN_START_ON		1
+#define TRAIN_TOGGLE		2
+#define TRAIN_BLOCK_STOPS	4
+
+/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS
+Trains are moving platforms that players can ride.
+The targets origin specifies the min point of the train at each corner.
+The train spawns at the first target it is pointing at.
+If the train is the target of a button or trigger, it will not begin moving until activated.
+speed	default 100
+dmg		default	2
+noise	looping sound to play when the train is in motion
+
+*/
+void train_next (edict_t *self);
+
+void train_blocked (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+
+	if (!self->dmg)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void train_wait (edict_t *self)
+{
+	if (self->target_ent->pathtarget)
+	{
+		char	*savetarget;
+		edict_t	*ent;
+
+		ent = self->target_ent;
+		savetarget = ent->target;
+		ent->target = ent->pathtarget;
+		G_UseTargets (ent, self->activator);
+		ent->target = savetarget;
+
+		// make sure we didn't get killed by a killtarget
+		if (!self->inuse)
+			return;
+	}
+
+	if (self->moveinfo.wait)
+	{
+		if (self->moveinfo.wait > 0)
+		{
+			self->nextthink = level.time + self->moveinfo.wait;
+			self->think = train_next;
+		}
+		else if (self->spawnflags & TRAIN_TOGGLE)  // && wait < 0
+		{
+			train_next (self);
+			self->spawnflags &= ~TRAIN_START_ON;
+			VectorClear (self->velocity);
+			self->nextthink = 0;
+		}
+
+		if (!(self->flags & FL_TEAMSLAVE))
+		{
+			if (self->moveinfo.sound_end)
+				gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0);
+			self->s.sound = 0;
+		}
+	}
+	else
+	{
+		train_next (self);
+	}
+	
+}
+
+void train_next (edict_t *self)
+{
+	edict_t		*ent;
+	vec3_t		dest;
+	qboolean	first;
+
+	first = true;
+again:
+	if (!self->target)
+	{
+//		gi.dprintf ("train_next: no next target\n");
+		return;
+	}
+
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_next: bad target %s\n", self->target);
+		return;
+	}
+
+	self->target = ent->target;
+
+	// check for a teleport path_corner
+	if (ent->spawnflags & 1)
+	{
+		if (!first)
+		{
+			gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin));
+			return;
+		}
+		first = false;
+		VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+		VectorCopy (self->s.origin, self->s.old_origin);
+		self->s.event = EV_OTHER_TELEPORT;
+		gi.linkentity (self);
+		goto again;
+	}
+
+	self->moveinfo.wait = ent->wait;
+	self->target_ent = ent;
+
+	if (!(self->flags & FL_TEAMSLAVE))
+	{
+		if (self->moveinfo.sound_start)
+			gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->s.sound = self->moveinfo.sound_middle;
+	}
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+}
+
+void train_resume (edict_t *self)
+{
+	edict_t	*ent;
+	vec3_t	dest;
+
+	ent = self->target_ent;
+
+	VectorSubtract (ent->s.origin, self->mins, dest);
+	self->moveinfo.state = STATE_TOP;
+	VectorCopy (self->s.origin, self->moveinfo.start_origin);
+	VectorCopy (dest, self->moveinfo.end_origin);
+	Move_Calc (self, dest, train_wait);
+	self->spawnflags |= TRAIN_START_ON;
+}
+
+void func_train_find (edict_t *self)
+{
+	edict_t *ent;
+
+	if (!self->target)
+	{
+		gi.dprintf ("train_find: no target\n");
+		return;
+	}
+	ent = G_PickTarget (self->target);
+	if (!ent)
+	{
+		gi.dprintf ("train_find: target %s not found\n", self->target);
+		return;
+	}
+	self->target = ent->target;
+
+	VectorSubtract (ent->s.origin, self->mins, self->s.origin);
+	gi.linkentity (self);
+
+	// if not triggered, start immediately
+	if (!self->targetname)
+		self->spawnflags |= TRAIN_START_ON;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		self->nextthink = level.time + FRAMETIME;
+		self->think = train_next;
+		self->activator = self;
+	}
+}
+
+void train_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (self->spawnflags & TRAIN_START_ON)
+	{
+		if (!(self->spawnflags & TRAIN_TOGGLE))
+			return;
+		self->spawnflags &= ~TRAIN_START_ON;
+		VectorClear (self->velocity);
+		self->nextthink = 0;
+	}
+	else
+	{
+		if (self->target_ent)
+			train_resume(self);
+		else
+			train_next(self);
+	}
+}
+
+void SP_func_train (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+
+	VectorClear (self->s.angles);
+	self->blocked = train_blocked;
+	if (self->spawnflags & TRAIN_BLOCK_STOPS)
+		self->dmg = 0;
+	else
+	{
+		if (!self->dmg)
+			self->dmg = 100;
+	}
+	self->solid = SOLID_BSP;
+	gi.setmodel (self, self->model);
+
+	if (st.noise)
+		self->moveinfo.sound_middle = gi.soundindex  (st.noise);
+
+	if (!self->speed)
+		self->speed = 100;
+
+	self->moveinfo.speed = self->speed;
+	self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed;
+
+	self->use = train_use;
+
+	gi.linkentity (self);
+
+	if (self->target)
+	{
+		// start trains on the second frame, to make sure their targets have had
+		// a chance to spawn
+		self->nextthink = level.time + FRAMETIME;
+		self->think = func_train_find;
+	}
+	else
+	{
+		gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin));
+	}
+}
+
+
+/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+*/
+void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *)
+{
+	edict_t *target;
+
+	if (self->movetarget->nextthink)
+	{
+//		gi.dprintf("elevator busy\n");
+		return;
+	}
+
+	if (!other->pathtarget)
+	{
+		gi.dprintf("elevator used with no pathtarget\n");
+		return;
+	}
+
+	target = G_PickTarget (other->pathtarget);
+	if (!target)
+	{
+		gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget);
+		return;
+	}
+
+	self->movetarget->target_ent = target;
+	train_resume (self->movetarget);
+}
+
+void trigger_elevator_init (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("trigger_elevator has no target\n");
+		return;
+	}
+	self->movetarget = G_PickTarget (self->target);
+	if (!self->movetarget)
+	{
+		gi.dprintf("trigger_elevator unable to find target %s\n", self->target);
+		return;
+	}
+	if (strcmp(self->movetarget->classname, "func_train") != 0)
+	{
+		gi.dprintf("trigger_elevator target %s is not a train\n", self->target);
+		return;
+	}
+
+	self->use = trigger_elevator_use;
+	self->svflags = SVF_NOCLIENT;
+
+}
+
+void SP_trigger_elevator (edict_t *self)
+{
+	self->think = trigger_elevator_init;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
+"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)
+
+"delay"			delay before first firing when turned on, default is 0
+
+"pausetime"		additional delay used only the very first time
+				and only if spawned with START_ON
+
+These can used but not touched.
+*/
+void func_timer_think (edict_t *self)
+{
+	G_UseTargets (self, self->activator);
+	self->nextthink = level.time + self->wait + crandom() * self->random;
+}
+
+void func_timer_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	// if on, turn it off
+	if (self->nextthink)
+	{
+		self->nextthink = 0;
+		return;
+	}
+
+	// turn it on
+	if (self->delay)
+		self->nextthink = level.time + self->delay;
+	else
+		func_timer_think (self);
+}
+
+void SP_func_timer (edict_t *self)
+{
+	if (!self->wait)
+		self->wait = 1.0;
+
+	self->use = func_timer_use;
+	self->think = func_timer_think;
+
+	if (self->random >= self->wait)
+	{
+		self->random = self->wait - FRAMETIME;
+		gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin));
+	}
+
+	if (self->spawnflags & 1)
+	{
+		self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random;
+		self->activator = self;
+	}
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE
+Conveyors are stationary brushes that move what's on them.
+The brush should be have a surface with at least one current content enabled.
+speed	default 100
+*/
+
+void func_conveyor_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & 1)
+	{
+		self->speed = 0;
+		self->spawnflags &= ~1;
+	}
+	else
+	{
+		self->speed = self->count;
+		self->spawnflags |= 1;
+	}
+
+	if (!(self->spawnflags & 2))
+		self->count = 0;
+}
+
+void SP_func_conveyor (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 100;
+
+	if (!(self->spawnflags & 1))
+	{
+		self->count = self->speed;
+		self->speed = 0;
+	}
+
+	self->use = func_conveyor_use;
+
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down
+A secret door.  Slide back and then to the side.
+
+open_once		doors never closes
+1st_left		1st move is left of arrow
+1st_down		1st move is down from arrow
+always_shoot	door is shootebale even if targeted
+
+"angle"		determines the direction
+"dmg"		damage to inflic when blocked (default 2)
+"wait"		how long to hold in the open position (default 5, -1 means hold)
+*/
+
+#define SECRET_ALWAYS_SHOOT	1
+#define SECRET_1ST_LEFT		2
+#define SECRET_1ST_DOWN		4
+
+void door_secret_move1 (edict_t *self);
+void door_secret_move2 (edict_t *self);
+void door_secret_move3 (edict_t *self);
+void door_secret_move4 (edict_t *self);
+void door_secret_move5 (edict_t *self);
+void door_secret_move6 (edict_t *self);
+void door_secret_done (edict_t *self);
+
+void door_secret_use (edict_t *self, edict_t *, edict_t *)
+{
+	// make sure we're not already moving
+	if (!VectorCompare(self->s.origin, vec3_origin))
+		return;
+
+	Move_Calc (self, self->pos1, door_secret_move1);
+	door_use_areaportals (self, true);
+}
+
+void door_secret_move1 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move2;
+}
+
+void door_secret_move2 (edict_t *self)
+{
+	Move_Calc (self, self->pos2, door_secret_move3);
+}
+
+void door_secret_move3 (edict_t *self)
+{
+	if (self->wait == -1)
+		return;
+	self->nextthink = level.time + self->wait;
+	self->think = door_secret_move4;
+}
+
+void door_secret_move4 (edict_t *self)
+{
+	Move_Calc (self, self->pos1, door_secret_move5);
+}
+
+void door_secret_move5 (edict_t *self)
+{
+	self->nextthink = level.time + 1.0;
+	self->think = door_secret_move6;
+}
+
+void door_secret_move6 (edict_t *self)
+{
+	Move_Calc (self, vec3_origin, door_secret_done);
+}
+
+void door_secret_done (edict_t *self)
+{
+	if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		self->health = 0;
+		self->takedamage = DAMAGE_YES;
+	}
+	door_use_areaportals (self, false);
+}
+
+void door_secret_blocked  (edict_t *self, edict_t *other)
+{
+	if (!(other->svflags & SVF_MONSTER) && (!other->client) )
+	{
+		// give it a chance to go away on it's own terms (like gibs)
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
+		// if it's still there, nuke it
+		if (other)
+			BecomeExplosion1 (other);
+		return;
+	}
+
+	if (level.time < self->touch_debounce_time)
+		return;
+	self->touch_debounce_time = level.time + 0.5;
+
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void door_secret_die (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	door_secret_use (self, attacker, attacker);
+}
+
+void SP_func_door_secret (edict_t *ent)
+{
+	vec3_t	forward, right, up;
+	float	side;
+	float	width;
+	float	length;
+
+	ent->moveinfo.sound_start = gi.soundindex  ("doors/dr1_strt.wav");
+	ent->moveinfo.sound_middle = gi.soundindex  ("doors/dr1_mid.wav");
+	ent->moveinfo.sound_end = gi.soundindex  ("doors/dr1_end.wav");
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	gi.setmodel (ent, ent->model);
+
+	ent->blocked = door_secret_blocked;
+	ent->use = door_secret_use;
+
+	if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT))
+	{
+		ent->health = 0;
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_secret_die;
+	}
+
+	if (!ent->dmg)
+		ent->dmg = 2;
+
+	if (!ent->wait)
+		ent->wait = 5;
+
+	ent->moveinfo.accel =
+	ent->moveinfo.decel =
+	ent->moveinfo.speed = 50;
+
+	// calculate positions
+	AngleVectors (ent->s.angles, forward, right, up);
+	VectorClear (ent->s.angles);
+	side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT);
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		width = fabs(DotProduct(up, ent->size));
+	else
+		width = fabs(DotProduct(right, ent->size));
+	length = fabs(DotProduct(forward, ent->size));
+	if (ent->spawnflags & SECRET_1ST_DOWN)
+		VectorMA (ent->s.origin, -1 * width, up, ent->pos1);
+	else
+		VectorMA (ent->s.origin, side * width, right, ent->pos1);
+	VectorMA (ent->pos1, length, forward, ent->pos2);
+
+	if (ent->health)
+	{
+		ent->takedamage = DAMAGE_YES;
+		ent->die = door_killed;
+		ent->max_health = ent->health;
+	}
+	else if (ent->targetname && ent->message)
+	{
+		gi.soundindex ("misc/talk.wav");
+		ent->touch = door_touch;
+	}
+	
+	ent->classname = "func_door";
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED func_killbox (1 0 0) ?
+Kills everything inside when fired, irrespective of protection.
+*/
+void use_killbox (edict_t *self, edict_t *, edict_t *)
+{
+	KillBox (self);
+}
+
+void SP_func_killbox (edict_t *ent)
+{
+	gi.setmodel (ent, ent->model);
+	ent->use = use_killbox;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+/*QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM
+"health"	if set, the light may be killed.
+*/
+
+// RAFAEL 
+// note to self
+// the lights will take damage from explosions
+// this could leave a player in total darkness very bad
+ 
+#define START_OFF	1
+
+void rotating_light_alarm (edict_t *self)
+{
+	if (self->spawnflags & START_OFF)
+	{
+		self->think = NULL;
+		self->nextthink = 0;	
+	}
+	else
+	{
+		gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0);
+		self->nextthink = level.time + 1;
+	}
+}
+
+void rotating_light_killed (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WELDING_SPARKS);
+	gi.WriteByte (30);
+	gi.WritePosition (self->s.origin);
+	gi.WriteDir (vec3_origin);
+	gi.WriteByte (0xe0 + (rand()&7));
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	self->s.effects &= ~EF_SPINNINGLIGHTS;
+	self->use = NULL;
+
+	self->think = G_FreeEdict;	
+	self->nextthink = level.time + 0.1;
+	
+}
+
+static void rotating_light_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & START_OFF)
+	{
+		self->spawnflags &= ~START_OFF;
+		self->s.effects |= EF_SPINNINGLIGHTS;
+
+		if (self->spawnflags & 2)
+		{
+			self->think = rotating_light_alarm;
+			self->nextthink = level.time + 0.1;
+		}
+	}
+	else
+	{
+		self->spawnflags |= START_OFF;
+		self->s.effects &= ~EF_SPINNINGLIGHTS;
+	}
+}
+	
+
+void SP_rotating_light (edict_t *self)
+{
+
+	self->movetype = MOVETYPE_STOP;
+	self->solid = SOLID_BBOX;
+	
+	self->s.modelindex = gi.modelindex ("models/objects/light/tris.md2");
+	
+	self->s.frame = 0;
+		
+	self->use = rotating_light_use;
+	
+	if (self->spawnflags & START_OFF)
+		self->s.effects &= ~EF_SPINNINGLIGHTS;
+	else
+	{
+		self->s.effects |= EF_SPINNINGLIGHTS;
+	}
+
+	if (!self->speed)
+		self->speed = 32;
+	// this is a real cheap way
+	// to set the radius of the light
+	// self->s.frame = self->speed;
+
+	if (!self->health)
+	{
+		self->health = 10;
+		self->max_health = self->health;
+		self->die = rotating_light_killed;
+		self->takedamage = DAMAGE_YES;
+	}
+	else
+	{
+		self->max_health = self->health;
+		self->die = rotating_light_killed;
+		self->takedamage = DAMAGE_YES;
+	}
+	
+	if (self->spawnflags & 2)
+	{
+		self->moveinfo.sound_start = gi.soundindex ("misc/alarm.wav");	
+	}
+	
+	gi.linkentity (self);
+
+}
+
+
+/*QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8) 
+object to be repaired.
+The default delay is 1 second
+"delay" the delay in seconds for spark to occur
+*/
+
+void object_repair_fx (edict_t *ent)
+{
+ 
+ 
+	ent->nextthink = level.time + ent->delay;
+
+	if (ent->health <= 100)
+		ent->health++;
+ 	else
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_WELDING_SPARKS);
+		gi.WriteByte (10);
+		gi.WritePosition (ent->s.origin);
+		gi.WriteDir (vec3_origin);
+		gi.WriteByte (0xe0 + (rand()&7));
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+	}
+	
+}
+
+
+void object_repair_dead (edict_t *ent)
+{
+	G_UseTargets (ent, ent);
+	ent->nextthink = level.time + 0.1;
+	ent->think = object_repair_fx;
+}
+
+void object_repair_sparks (edict_t *ent)
+{
+ 
+	if (ent->health < 0)
+	{
+		ent->nextthink = level.time + 0.1;
+		ent->think = object_repair_dead;
+		return;
+	}
+
+	ent->nextthink = level.time + ent->delay;
+	
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WELDING_SPARKS);
+	gi.WriteByte (10);
+	gi.WritePosition (ent->s.origin);
+	gi.WriteDir (vec3_origin);
+	gi.WriteByte (0xe0 + (rand()&7));
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	
+}
+
+void SP_object_repair (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->classname = "object_repair";
+	VectorSet (ent->mins, -8, -8, 8);
+	VectorSet (ent->maxs, 8, 8, 8);
+	ent->think = object_repair_sparks;
+	ent->nextthink = level.time + 1.0;
+	ent->health = 100;
+	if (!ent->delay)
+		ent->delay = 1.0;
+	
+}
+
--- /dev/null
+++ b/xatrix/g_items.c
@@ -1,0 +1,2450 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+qboolean	Pickup_Weapon (edict_t *ent, edict_t *other);
+void		Use_Weapon (edict_t *ent, gitem_t *inv);
+void		Use_Weapon2 (edict_t *ent, gitem_t *inv);
+void		Drop_Weapon (edict_t *ent, gitem_t *inv);
+
+void Weapon_Blaster (edict_t *ent);
+void Weapon_Shotgun (edict_t *ent);
+void Weapon_SuperShotgun (edict_t *ent);
+void Weapon_Machinegun (edict_t *ent);
+void Weapon_Chaingun (edict_t *ent);
+void Weapon_HyperBlaster (edict_t *ent);
+void Weapon_RocketLauncher (edict_t *ent);
+void Weapon_Grenade (edict_t *ent);
+void Weapon_GrenadeLauncher (edict_t *ent);
+void Weapon_Railgun (edict_t *ent);
+void Weapon_BFG (edict_t *ent);
+// RAFAEL
+void Weapon_Ionripper (edict_t *ent);
+void Weapon_Phalanx (edict_t *ent);
+void Weapon_Trap (edict_t *ent);
+
+gitem_armor_t jacketarmor_info	= { 25,  50, .30, .00, ARMOR_JACKET};
+gitem_armor_t combatarmor_info	= { 50, 100, .60, .30, ARMOR_COMBAT};
+gitem_armor_t bodyarmor_info	= {100, 200, .80, .60, ARMOR_BODY};
+
+static int	jacket_armor_index;
+static int	combat_armor_index;
+static int	body_armor_index;
+static int	power_screen_index;
+static int	power_shield_index;
+
+#define HEALTH_IGNORE_MAX	1
+#define HEALTH_TIMED		2
+
+void Use_Quad (edict_t *ent, gitem_t *item);
+// RAFAEL
+void Use_QuadFire (edict_t *ent, gitem_t *item);
+
+static int	quad_drop_timeout_hack;
+// RAFAEL
+static int	quad_fire_drop_timeout_hack;
+
+//======================================================================
+
+/*
+===============
+GetItemByIndex
+===============
+*/
+gitem_t	*GetItemByIndex (int index)
+{
+	if (index == 0 || index >= game.num_items)
+		return NULL;
+
+	return &itemlist[index];
+}
+
+
+/*
+===============
+FindItemByClassname
+
+===============
+*/
+gitem_t	*FindItemByClassname (char *classname)
+{
+	int		i;
+	gitem_t	*it;
+
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		if (!it->classname)
+			continue;
+		if (!cistrcmp(it->classname, classname))
+			return it;
+	}
+
+	return NULL;
+}
+
+/*
+===============
+FindItem
+
+===============
+*/
+gitem_t	*FindItem (char *pickup_name)
+{
+	int		i;
+	gitem_t	*it;
+
+	it = itemlist;
+	for (i=0 ; i<game.num_items ; i++, it++)
+	{
+		if (!it->pickup_name)
+			continue;
+		if (!cistrcmp(it->pickup_name, pickup_name))
+			return it;
+	}
+
+	return NULL;
+}
+
+//======================================================================
+
+void DoRespawn (edict_t *ent)
+{
+	if (ent->team)
+	{
+		edict_t	*master;
+		int	count;
+		int choice;
+
+		master = ent->teammaster;
+
+		for (count = 0, ent = master; ent; ent = ent->chain, count++)
+			;
+
+		choice = rand() % count;
+
+		for (count = 0, ent = master; count < choice; ent = ent->chain, count++)
+			;
+	}
+
+	ent->svflags &= ~SVF_NOCLIENT;
+	ent->solid = SOLID_TRIGGER;
+	gi.linkentity (ent);
+
+	// send an effect
+	ent->s.event = EV_ITEM_RESPAWN;
+}
+
+void SetRespawn (edict_t *ent, float delay)
+{
+	ent->flags |= FL_RESPAWN;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->solid = SOLID_NOT;
+	ent->nextthink = level.time + delay;
+	ent->think = DoRespawn;
+	gi.linkentity (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Powerup (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+	if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1))
+		return false;
+
+	if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0))
+		return false;
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+		if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+		{
+			if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))
+				quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME;
+			ent->item->use (other, ent->item);
+		}
+		// RAFAEL
+		else if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM)))
+		{
+			if ((ent->item->use == Use_QuadFire) && (ent->spawnflags & DROPPED_PLAYER_ITEM))
+				quad_fire_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME;
+			ent->item->use (other, ent->item);
+		}
+	}
+
+	return true;
+}
+
+void Drop_General (edict_t *ent, gitem_t *item)
+{
+	Drop_Item (ent, item);
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other)
+{
+	if (!deathmatch->value)
+		other->max_health += 1;
+
+	if (other->health < other->max_health)
+		other->health = other->max_health;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_AncientHead (edict_t *ent, edict_t *other)
+{
+	other->max_health += 2;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_Bandolier (edict_t *ent, edict_t *other)
+{
+	gitem_t	*item;
+	int		index;
+
+	if (other->client->pers.max_bullets < 250)
+		other->client->pers.max_bullets = 250;
+	if (other->client->pers.max_shells < 150)
+		other->client->pers.max_shells = 150;
+	if (other->client->pers.max_cells < 250)
+		other->client->pers.max_cells = 250;
+	if (other->client->pers.max_slugs < 75)
+		other->client->pers.max_slugs = 75;
+	// RAFAEL
+	if (other->client->pers.max_magslug < 75)
+		other->client->pers.max_magslug = 75;
+
+	item = FindItem("Bullets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+			other->client->pers.inventory[index] = other->client->pers.max_bullets;
+	}
+
+	item = FindItem("Shells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+			other->client->pers.inventory[index] = other->client->pers.max_shells;
+	}
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+qboolean Pickup_Pack (edict_t *ent, edict_t *other)
+{
+	gitem_t	*item;
+	int		index;
+
+	if (other->client->pers.max_bullets < 300)
+		other->client->pers.max_bullets = 300;
+	if (other->client->pers.max_shells < 200)
+		other->client->pers.max_shells = 200;
+	if (other->client->pers.max_rockets < 100)
+		other->client->pers.max_rockets = 100;
+	if (other->client->pers.max_grenades < 100)
+		other->client->pers.max_grenades = 100;
+	if (other->client->pers.max_cells < 300)
+		other->client->pers.max_cells = 300;
+	if (other->client->pers.max_slugs < 100)
+		other->client->pers.max_slugs = 100;
+	// RAFAEL
+	if (other->client->pers.max_magslug < 100)
+		other->client->pers.max_magslug = 100;
+
+	item = FindItem("Bullets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_bullets)
+			other->client->pers.inventory[index] = other->client->pers.max_bullets;
+	}
+
+	item = FindItem("Shells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_shells)
+			other->client->pers.inventory[index] = other->client->pers.max_shells;
+	}
+
+	item = FindItem("Cells");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_cells)
+			other->client->pers.inventory[index] = other->client->pers.max_cells;
+	}
+
+	item = FindItem("Grenades");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_grenades)
+			other->client->pers.inventory[index] = other->client->pers.max_grenades;
+	}
+
+	item = FindItem("Rockets");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_rockets)
+			other->client->pers.inventory[index] = other->client->pers.max_rockets;
+	}
+
+	item = FindItem("Slugs");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_slugs)
+			other->client->pers.inventory[index] = other->client->pers.max_slugs;
+	}
+
+	// RAFAEL
+	item = FindItem ("Mag Slug");
+	if (item)
+	{
+		index = ITEM_INDEX(item);
+		other->client->pers.inventory[index] += item->quantity;
+		if (other->client->pers.inventory[index] > other->client->pers.max_magslug)
+			other->client->pers.inventory[index] = other->client->pers.max_magslug;
+	}
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, ent->item->quantity);
+
+	return true;
+}
+
+//======================================================================
+
+void Use_Quad (edict_t *ent, gitem_t *item)
+{
+	int		timeout;
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (quad_drop_timeout_hack)
+	{
+		timeout = quad_drop_timeout_hack;
+		quad_drop_timeout_hack = 0;
+	}
+	else
+	{
+		timeout = 300;
+	}
+
+	if (ent->client->quad_framenum > level.framenum)
+		ent->client->quad_framenum += timeout;
+	else
+		ent->client->quad_framenum = level.framenum + timeout;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+
+// =====================================================================
+
+// RAFAEL
+void Use_QuadFire (edict_t *ent, gitem_t *item)
+{
+	int		timeout;
+
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (quad_fire_drop_timeout_hack)
+	{
+		timeout = quad_fire_drop_timeout_hack;
+		quad_fire_drop_timeout_hack = 0;
+	}
+	else
+	{
+		timeout = 300;
+	}
+
+	if (ent->client->quadfire_framenum > level.framenum)
+		ent->client->quadfire_framenum += timeout;
+	else
+		ent->client->quadfire_framenum = level.framenum + timeout;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0);
+}
+
+
+//======================================================================
+
+void Use_Breather (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->breather_framenum > level.framenum)
+		ent->client->breather_framenum += 300;
+	else
+		ent->client->breather_framenum = level.framenum + 300;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void Use_Envirosuit (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->enviro_framenum > level.framenum)
+		ent->client->enviro_framenum += 300;
+	else
+		ent->client->enviro_framenum = level.framenum + 300;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void	Use_Invulnerability (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+
+	if (ent->client->invincible_framenum > level.framenum)
+		ent->client->invincible_framenum += 300;
+	else
+		ent->client->invincible_framenum = level.framenum + 300;
+
+	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+void	Use_Silencer (edict_t *ent, gitem_t *item)
+{
+	ent->client->pers.inventory[ITEM_INDEX(item)]--;
+	ValidateSelectedItem (ent);
+	ent->client->silencer_shots += 30;
+
+//	gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0);
+}
+
+//======================================================================
+
+qboolean Pickup_Key (edict_t *ent, edict_t *other)
+{
+	if (coop->value)
+	{
+		if (strcmp(ent->classname, "key_power_cube") == 0)
+		{
+			if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8))
+				return false;
+			other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+			other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8);
+		}
+		else
+		{
+			if (other->client->pers.inventory[ITEM_INDEX(ent->item)])
+				return false;
+			other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1;
+		}
+		return true;
+	}
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+	return true;
+}
+
+//======================================================================
+
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count)
+{
+	int			index;
+	int			max;
+
+	if (!ent->client)
+		return false;
+
+	if (item->tag == AMMO_BULLETS)
+		max = ent->client->pers.max_bullets;
+	else if (item->tag == AMMO_SHELLS)
+		max = ent->client->pers.max_shells;
+	else if (item->tag == AMMO_ROCKETS)
+		max = ent->client->pers.max_rockets;
+	else if (item->tag == AMMO_GRENADES)
+		max = ent->client->pers.max_grenades;
+	else if (item->tag == AMMO_CELLS)
+		max = ent->client->pers.max_cells;
+	else if (item->tag == AMMO_SLUGS)
+		max = ent->client->pers.max_slugs;
+	// RAFAEL
+	else if (item->tag == AMMO_MAGSLUG)
+		max = ent->client->pers.max_magslug;
+	// RAFAEL
+	else if (item->tag == AMMO_TRAP)
+		max = ent->client->pers.max_trap;
+	else
+		return false;
+
+	index = ITEM_INDEX(item);
+
+	if (ent->client->pers.inventory[index] == max)
+		return false;
+
+	ent->client->pers.inventory[index] += count;
+
+	if (ent->client->pers.inventory[index] > max)
+		ent->client->pers.inventory[index] = max;
+
+	return true;
+}
+
+qboolean Pickup_Ammo (edict_t *ent, edict_t *other)
+{
+	int			oldcount;
+	int			count;
+	qboolean	weapon;
+
+	weapon = (ent->item->flags & IT_WEAPON);
+	if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		count = 1000;
+	else if (ent->count)
+		count = ent->count;
+	else
+		count = ent->item->quantity;
+
+	oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+	if (!Add_Ammo (other, ent->item, count))
+		return false;
+
+	if (weapon && !oldcount)
+	{
+		if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) )
+			other->client->newweapon = ent->item;
+	}
+
+	if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value))
+		SetRespawn (ent, 30);
+	return true;
+}
+
+void Drop_Ammo (edict_t *ent, gitem_t *item)
+{
+	edict_t	*dropped;
+	int		index;
+
+	index = ITEM_INDEX(item);
+	dropped = Drop_Item (ent, item);
+	if (ent->client->pers.inventory[index] >= item->quantity)
+		dropped->count = item->quantity;
+	else
+		dropped->count = ent->client->pers.inventory[index];
+
+	if (ent->client->pers.weapon && 
+		ent->client->pers.weapon->tag == AMMO_GRENADES &&
+		item->tag == AMMO_GRENADES &&
+		ent->client->pers.inventory[index] - dropped->count <= 0) {
+		gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n");
+		G_FreeEdict(dropped);
+		return;
+	}
+
+	ent->client->pers.inventory[index] -= dropped->count;
+	ValidateSelectedItem (ent);
+}
+
+
+//======================================================================
+
+void MegaHealth_think (edict_t *self)
+{
+	if (self->owner->health > self->owner->max_health)
+	{
+		self->nextthink = level.time + 1;
+		self->owner->health -= 1;
+		return;
+	}
+
+	if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (self, 20);
+	else
+		G_FreeEdict (self);
+}
+
+qboolean Pickup_Health (edict_t *ent, edict_t *other)
+{
+	if (!(ent->style & HEALTH_IGNORE_MAX))
+		if (other->health >= other->max_health)
+			return false;
+
+	other->health += ent->count;
+
+	if (!(ent->style & HEALTH_IGNORE_MAX))
+	{
+		if (other->health > other->max_health)
+			other->health = other->max_health;
+	}
+
+	if (ent->style & HEALTH_TIMED)
+	{
+		ent->think = MegaHealth_think;
+		ent->nextthink = level.time + 5;
+		ent->owner = other;
+		ent->flags |= FL_RESPAWN;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+	}
+	else
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+			SetRespawn (ent, 30);
+	}
+
+	return true;
+}
+
+//======================================================================
+
+int ArmorIndex (edict_t *ent)
+{
+	if (!ent->client)
+		return 0;
+
+	if (ent->client->pers.inventory[jacket_armor_index] > 0)
+		return jacket_armor_index;
+
+	if (ent->client->pers.inventory[combat_armor_index] > 0)
+		return combat_armor_index;
+
+	if (ent->client->pers.inventory[body_armor_index] > 0)
+		return body_armor_index;
+
+	return 0;
+}
+
+qboolean Pickup_Armor (edict_t *ent, edict_t *other)
+{
+	int				old_armor_index;
+	gitem_armor_t	*oldinfo;
+	gitem_armor_t	*newinfo;
+	int				newcount;
+	float			salvage;
+	int				salvagecount;
+
+	// get info on new armor
+	newinfo = (gitem_armor_t *)ent->item->info;
+
+	old_armor_index = ArmorIndex (other);
+
+	// handle armor shards specially
+	if (ent->item->tag == ARMOR_SHARD)
+	{
+		if (!old_armor_index)
+			other->client->pers.inventory[jacket_armor_index] = 2;
+		else
+			other->client->pers.inventory[old_armor_index] += 2;
+	}
+
+	// if player has no armor, just use it
+	else if (!old_armor_index)
+	{
+		other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count;
+	}
+
+	// use the better armor
+	else
+	{
+		// get info on old armor
+		if (old_armor_index == jacket_armor_index)
+			oldinfo = &jacketarmor_info;
+		else if (old_armor_index == combat_armor_index)
+			oldinfo = &combatarmor_info;
+		else // (old_armor_index == body_armor_index)
+			oldinfo = &bodyarmor_info;
+
+		if (newinfo->normal_protection > oldinfo->normal_protection)
+		{
+			// calc new armor values
+			salvage = oldinfo->normal_protection / newinfo->normal_protection;
+			salvagecount = salvage * other->client->pers.inventory[old_armor_index];
+			newcount = newinfo->base_count + salvagecount;
+			if (newcount > newinfo->max_count)
+				newcount = newinfo->max_count;
+
+			// zero count of old armor so it goes away
+			other->client->pers.inventory[old_armor_index] = 0;
+
+			// change armor to new item with computed value
+			other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount;
+		}
+		else
+		{
+			// calc new armor values
+			salvage = newinfo->normal_protection / oldinfo->normal_protection;
+			salvagecount = salvage * newinfo->base_count;
+			newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
+			if (newcount > oldinfo->max_count)
+				newcount = oldinfo->max_count;
+
+			// if we're already maxed out then we don't need the new armor
+			if (other->client->pers.inventory[old_armor_index] >= newcount)
+				return false;
+
+			// update current armor value
+			other->client->pers.inventory[old_armor_index] = newcount;
+		}
+	}
+
+	if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value))
+		SetRespawn (ent, 20);
+
+	return true;
+}
+
+//======================================================================
+
+int PowerArmorType (edict_t *ent)
+{
+	if (!ent->client)
+		return POWER_ARMOR_NONE;
+
+	if (!(ent->flags & FL_POWER_ARMOR))
+		return POWER_ARMOR_NONE;
+
+	if (ent->client->pers.inventory[power_shield_index] > 0)
+		return POWER_ARMOR_SHIELD;
+
+	if (ent->client->pers.inventory[power_screen_index] > 0)
+		return POWER_ARMOR_SCREEN;
+
+	return POWER_ARMOR_NONE;
+}
+
+void Use_PowerArmor (edict_t *ent, gitem_t *)
+{
+	int		index;
+
+	if (ent->flags & FL_POWER_ARMOR)
+	{
+		ent->flags &= ~FL_POWER_ARMOR;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		index = ITEM_INDEX(FindItem("cells"));
+		if (!ent->client->pers.inventory[index])
+		{
+			gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n");
+			return;
+		}
+		ent->flags |= FL_POWER_ARMOR;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);
+	}
+}
+
+qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other)
+{
+	int		quantity;
+
+	quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)];
+
+	other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
+
+	if (deathmatch->value)
+	{
+		if (!(ent->spawnflags & DROPPED_ITEM) )
+			SetRespawn (ent, ent->item->quantity);
+		// auto-use for DM only if we didn't already have one
+		if (!quantity)
+			ent->item->use (other, ent->item);
+	}
+
+	return true;
+}
+
+void Drop_PowerArmor (edict_t *ent, gitem_t *item)
+{
+	if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1))
+		Use_PowerArmor (ent, item);
+	Drop_General (ent, item);
+}
+
+//======================================================================
+
+/*
+===============
+Touch_Item
+===============
+*/
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *, csurface_t *)
+{
+	qboolean	taken;
+
+	if (!other->client)
+		return;
+	if (other->health < 1)
+		return;		// dead people can't pickup
+	if (!ent->item->pickup)
+		return;		// not a grabbable item?
+
+	taken = ent->item->pickup(ent, other);
+
+	if (taken)
+	{
+		// flash the screen
+		other->client->bonus_alpha = 0.25;	
+
+		// show icon and name on status bar
+		other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon);
+		other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item);
+		other->client->pickup_msg_time = level.time + 3.0;
+
+		// change selected item
+		if (ent->item->use)
+			other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);
+
+		if (ent->item->pickup == Pickup_Health)
+		{
+			if (ent->count == 2)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0);
+			else if (ent->count == 10)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0);
+			else if (ent->count == 25)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0);
+			else // (ent->count == 100)
+				gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0);
+		}
+		else if (ent->item->pickup_sound)
+		{
+			gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0);
+		}
+	}
+
+	if (!(ent->spawnflags & ITEM_TARGETS_USED))
+	{
+		G_UseTargets (ent, other);
+		ent->spawnflags |= ITEM_TARGETS_USED;
+	}
+
+	if (!taken)
+		return;
+
+	if (!((coop->value) &&  (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)))
+	{
+		if (ent->flags & FL_RESPAWN)
+			ent->flags &= ~FL_RESPAWN;
+		else
+			G_FreeEdict (ent);
+	}
+}
+
+//======================================================================
+
+static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	Touch_Item (ent, other, plane, surf);
+}
+
+static void drop_make_touchable (edict_t *ent)
+{
+	ent->touch = Touch_Item;
+	if (deathmatch->value)
+	{
+		ent->nextthink = level.time + 29;
+		ent->think = G_FreeEdict;
+	}
+}
+
+edict_t *Drop_Item (edict_t *ent, gitem_t *item)
+{
+	edict_t	*dropped;
+	vec3_t	forward, right;
+	vec3_t	offset;
+
+	dropped = G_Spawn();
+
+	dropped->classname = item->classname;
+	dropped->item = item;
+	dropped->spawnflags = DROPPED_ITEM;
+	dropped->s.effects = item->world_model_flags;
+	dropped->s.renderfx = RF_GLOW;
+	VectorSet (dropped->mins, -15, -15, -15);
+	VectorSet (dropped->maxs, 15, 15, 15);
+	gi.setmodel (dropped, dropped->item->world_model);
+	dropped->solid = SOLID_TRIGGER;
+	dropped->movetype = MOVETYPE_TOSS;  
+	dropped->touch = drop_temp_touch;
+	dropped->owner = ent;
+
+	if (ent->client)
+	{
+		trace_t	trace;
+
+		AngleVectors (ent->client->v_angle, forward, right, NULL);
+		VectorSet(offset, 24, 0, -16);
+		G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin);
+		trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs,
+			dropped->s.origin, ent, CONTENTS_SOLID);
+		VectorCopy (trace.endpos, dropped->s.origin);
+	}
+	else
+	{
+		AngleVectors (ent->s.angles, forward, right, NULL);
+		VectorCopy (ent->s.origin, dropped->s.origin);
+	}
+
+	VectorScale (forward, 100, dropped->velocity);
+	dropped->velocity[2] = 300;
+
+	dropped->think = drop_make_touchable;
+	dropped->nextthink = level.time + 1;
+
+	gi.linkentity (dropped);
+
+	return dropped;
+}
+
+void Use_Item (edict_t *ent, edict_t *, edict_t *)
+{
+	ent->svflags &= ~SVF_NOCLIENT;
+	ent->use = NULL;
+
+	if (ent->spawnflags & ITEM_NO_TOUCH)
+	{
+		ent->solid = SOLID_BBOX;
+		ent->touch = NULL;
+	}
+	else
+	{
+		ent->solid = SOLID_TRIGGER;
+		ent->touch = Touch_Item;
+	}
+
+	gi.linkentity (ent);
+}
+
+//======================================================================
+
+/*
+================
+droptofloor
+================
+*/
+void droptofloor (edict_t *ent)
+{
+	trace_t		tr;
+	vec3_t		dest;
+	float		*v;
+
+	v = tv(-15,-15,-15);
+	VectorCopy (v, ent->mins);
+	v = tv(15,15,15);
+	VectorCopy (v, ent->maxs);
+
+	if (ent->model)
+		gi.setmodel (ent, ent->model);
+	else
+		gi.setmodel (ent, ent->item->world_model);
+	ent->solid = SOLID_TRIGGER;
+	ent->movetype = MOVETYPE_TOSS;  
+	ent->touch = Touch_Item;
+
+	v = tv(0,0,-128);
+	VectorAdd (ent->s.origin, v, dest);
+
+	tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
+	if (tr.startsolid)
+	{
+		// RAFAEL
+		if (strcmp (ent->classname, "foodcube") == 0)
+		{
+			VectorCopy (ent->s.origin, tr.endpos);
+			ent->velocity[2] = 0;
+		}
+		else
+		{
+			gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
+			G_FreeEdict (ent);
+			return;
+		}
+	}
+
+	VectorCopy (tr.endpos, ent->s.origin);
+
+	if (ent->team)
+	{
+		ent->flags &= ~FL_TEAMSLAVE;
+		ent->chain = ent->teamchain;
+		ent->teamchain = NULL;
+
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+		if (ent == ent->teammaster)
+		{
+			ent->nextthink = level.time + FRAMETIME;
+			ent->think = DoRespawn;
+		}
+	}
+
+	if (ent->spawnflags & ITEM_NO_TOUCH)
+	{
+		ent->solid = SOLID_BBOX;
+		ent->touch = NULL;
+		ent->s.effects &= ~EF_ROTATE;
+		ent->s.renderfx &= ~RF_GLOW;
+	}
+
+	if (ent->spawnflags & ITEM_TRIGGER_SPAWN)
+	{
+		ent->svflags |= SVF_NOCLIENT;
+		ent->solid = SOLID_NOT;
+		ent->use = Use_Item;
+	}
+
+	gi.linkentity (ent);
+}
+
+
+/*
+===============
+PrecacheItem
+
+Precaches all data needed for a given item.
+This will be called for each item spawned in a level,
+and for each item in each client's inventory.
+===============
+*/
+void PrecacheItem (gitem_t *it)
+{
+	char	*s, *start;
+	char	data[MAX_QPATH];
+	int		len;
+	gitem_t	*ammo;
+
+	if (!it)
+		return;
+
+	if (it->pickup_sound)
+		gi.soundindex (it->pickup_sound);
+	if (it->world_model)
+		gi.modelindex (it->world_model);
+	if (it->view_model)
+		gi.modelindex (it->view_model);
+	if (it->icon)
+		gi.imageindex (it->icon);
+
+	// parse everything for its ammo
+	if (it->ammo && it->ammo[0])
+	{
+		ammo = FindItem (it->ammo);
+		if (ammo != it)
+			PrecacheItem (ammo);
+	}
+
+	// parse the space seperated precache string for other items
+	s = it->precaches;
+	if (!s || !s[0])
+		return;
+
+	while (*s)
+	{
+		start = s;
+		while (*s && *s != ' ')
+			s++;
+
+		len = s-start;
+		if (len >= MAX_QPATH || len < 5)
+			gi.error ("PrecacheItem: %s has bad precache string", it->classname);
+		memcpy (data, start, len);
+		data[len] = 0;
+		if (*s)
+			s++;
+
+		// determine type based on extension
+		if (!strcmp(data+len-3, "md2"))
+			gi.modelindex (data);
+		else if (!strcmp(data+len-3, "sp2"))
+			gi.modelindex (data);
+		else if (!strcmp(data+len-3, "wav"))
+			gi.soundindex (data);
+		if (!strcmp(data+len-3, "pcx"))
+			gi.imageindex (data);
+	}
+}
+
+/*
+============
+SpawnItem
+
+Sets the clipping size and plants the object on the floor.
+
+Items can't be immediately dropped to floor, because they might
+be on an entity that hasn't spawned yet.
+============
+*/
+void SpawnItem (edict_t *ent, gitem_t *item)
+{
+	PrecacheItem (item);
+
+	if (ent->spawnflags)
+	{
+		if (strcmp(ent->classname, "key_power_cube") != 0)
+		{
+			ent->spawnflags = 0;
+			gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin));
+		}
+	}
+
+	// some items will be prevented in deathmatch
+	if (deathmatch->value)
+	{
+		if ( (int)dmflags->value & DF_NO_ARMOR )
+		{
+			if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_NO_ITEMS )
+		{
+			if (item->pickup == Pickup_Powerup)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_NO_HEALTH )
+		{
+			if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead)
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+		if ( (int)dmflags->value & DF_INFINITE_AMMO )
+		{
+			if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) )
+			{
+				G_FreeEdict (ent);
+				return;
+			}
+		}
+	}
+
+	if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0))
+	{
+		ent->spawnflags |= (1 << (8 + level.power_cubes));
+		level.power_cubes++;
+	}
+
+	// don't let them drop items that stay in a coop game
+	if ((coop->value) && (item->flags & IT_STAY_COOP))
+	{
+		item->drop = NULL;
+	}
+
+	ent->item = item;
+	ent->nextthink = level.time + 2 * FRAMETIME;    // items start after other solids
+	ent->think = droptofloor;
+	ent->s.effects = item->world_model_flags;
+	ent->s.renderfx = RF_GLOW;
+	if (ent->model)
+		gi.modelindex (ent->model);
+}
+
+//======================================================================
+
+gitem_t	itemlist[] = 
+{
+	{
+		NULL
+	},	// leave index 0 alone
+
+	//
+	// ARMOR
+	//
+
+/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_armor_body", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/body/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_bodyarmor",
+/* pickup */	"Body Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&bodyarmor_info,
+		ARMOR_BODY,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_armor_combat", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/combat/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_combatarmor",
+/* pickup */	"Combat Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&combatarmor_info,
+		ARMOR_COMBAT,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_armor_jacket", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar1_pkup.wav",
+		"models/items/armor/jacket/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_jacketarmor",
+/* pickup */	"Jacket Armor",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		&jacketarmor_info,
+		ARMOR_JACKET,
+/* precache */ ""
+	},
+
+/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_armor_shard", 
+		Pickup_Armor,
+		NULL,
+		NULL,
+		NULL,
+		"misc/ar2_pkup.wav",
+		"models/items/armor/shard/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_jacketarmor",
+/* pickup */	"Armor Shard",
+/* width */		3,
+		0,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		ARMOR_SHARD,
+/* precache */ ""
+	},
+
+
+/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_power_screen", 
+		Pickup_PowerArmor,
+		Use_PowerArmor,
+		Drop_PowerArmor,
+		NULL,
+		"misc/ar3_pkup.wav",
+		"models/items/armor/screen/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_powerscreen",
+/* pickup */	"Power Screen",
+/* width */		0,
+		60,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_power_shield",
+		Pickup_PowerArmor,
+		Use_PowerArmor,
+		Drop_PowerArmor,
+		NULL,
+		"misc/ar3_pkup.wav",
+		"models/items/armor/shield/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_powershield",
+/* pickup */	"Power Shield",
+/* width */		0,
+		60,
+		NULL,
+		IT_ARMOR,
+		0,
+		NULL,
+		0,
+/* precache */ "misc/power2.wav misc/power1.wav"
+	},
+
+
+	//
+	// WEAPONS 
+	//
+
+/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+always owned, never in the world
+*/
+	{
+		"weapon_blaster", 
+		NULL,
+		Use_Weapon,
+		NULL,
+		Weapon_Blaster,
+		"misc/w_pkup.wav",
+		NULL, 0,
+		"models/weapons/v_blast/tris.md2",
+/* icon */		"w_blaster",
+/* pickup */	"Blaster",
+		0,
+		0,
+		NULL,
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_BLASTER,
+		NULL,
+		0,
+/* precache */ "weapons/blastf1a.wav misc/lasfly.wav"
+	},
+
+/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_shotgun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Shotgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_shotg/tris.md2", EF_ROTATE,
+		"models/weapons/v_shotg/tris.md2",
+/* icon */		"w_shotgun",
+/* pickup */	"Shotgun",
+		0,
+		1,
+		"Shells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_SHOTGUN,
+		NULL,
+		0,
+/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav"
+	},
+
+/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_supershotgun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_SuperShotgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_shotg2/tris.md2", EF_ROTATE,
+		"models/weapons/v_shotg2/tris.md2",
+/* icon */		"w_sshotgun",
+/* pickup */	"Super Shotgun",
+		0,
+		2,
+		"Shells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_SUPERSHOTGUN,
+		NULL,
+		0,
+/* precache */ "weapons/sshotf1b.wav"
+	},
+
+/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_machinegun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Machinegun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_machn/tris.md2", EF_ROTATE,
+		"models/weapons/v_machn/tris.md2",
+/* icon */		"w_machinegun",
+/* pickup */	"Machinegun",
+		0,
+		1,
+		"Bullets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_MACHINEGUN,
+		NULL,
+		0,
+/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"
+	},
+
+/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_chaingun", 
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Chaingun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_chain/tris.md2", EF_ROTATE,
+		"models/weapons/v_chain/tris.md2",
+/* icon */		"w_chaingun",
+/* pickup */	"Chaingun",
+		0,
+		1,
+		"Bullets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_CHAINGUN,
+		NULL,
+		0,
+/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"
+	},
+
+/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_grenades",
+		Pickup_Ammo,
+		Use_Weapon,
+		Drop_Ammo,
+		Weapon_Grenade,
+		"misc/am_pkup.wav",
+		"models/items/ammo/grenades/medium/tris.md2", 0,
+		"models/weapons/v_handgr/tris.md2",
+/* icon */		"a_grenades",
+/* pickup */	"Grenades",
+/* width */		3,
+		5,
+		"grenades",
+		IT_AMMO|IT_WEAPON,
+		WEAP_GRENADES,
+		NULL,
+		AMMO_GRENADES,
+/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+	},
+
+/*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_trap",
+		Pickup_Ammo,
+		Use_Weapon,
+		Drop_Ammo,
+		Weapon_Trap,
+		"misc/am_pkup.wav",
+		"models/weapons/g_trap/tris.md2", EF_ROTATE,
+		"models/weapons/v_trap/tris.md2",
+/* icon */		"a_trap",
+/* pickup */	"Trap",
+/* width */		3,
+		1,
+		"trap",
+		IT_AMMO|IT_WEAPON,
+		0,
+		NULL,
+		AMMO_TRAP,
+/* precache */ "weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav"
+// "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "
+	},
+
+	
+/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_grenadelauncher",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_GrenadeLauncher,
+		"misc/w_pkup.wav",
+		"models/weapons/g_launch/tris.md2", EF_ROTATE,
+		"models/weapons/v_launch/tris.md2",
+/* icon */		"w_glauncher",
+/* pickup */	"Grenade Launcher",
+		0,
+		1,
+		"Grenades",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_GRENADELAUNCHER,
+		NULL,
+		0,
+/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"
+	},
+
+/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_rocketlauncher",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_RocketLauncher,
+		"misc/w_pkup.wav",
+		"models/weapons/g_rocket/tris.md2", EF_ROTATE,
+		"models/weapons/v_rocket/tris.md2",
+/* icon */		"w_rlauncher",
+/* pickup */	"Rocket Launcher",
+		0,
+		1,
+		"Rockets",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_ROCKETLAUNCHER,
+		NULL,
+		0,
+/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"
+	},
+
+/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_hyperblaster", 
+		Pickup_Weapon,
+		// RAFAEL
+		Use_Weapon2,
+		Drop_Weapon,
+		Weapon_HyperBlaster,
+		"misc/w_pkup.wav",
+		"models/weapons/g_hyperb/tris.md2", EF_ROTATE,
+		"models/weapons/v_hyperb/tris.md2",
+/* icon */		"w_hyperblaster",
+/* pickup */	"HyperBlaster",
+		0,
+		1,
+		"Cells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_HYPERBLASTER,
+		NULL,
+		0,
+/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"
+	},
+
+/*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+
+	{
+		"weapon_boomer",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Ionripper,
+		"misc/w_pkup.wav",
+		"models/weapons/g_boom/tris.md2", EF_ROTATE,
+		"models/weapons/v_boomer/tris.md2",
+/* icon */	"w_ripper",
+/* pickup */ "Ionripper",
+		0,
+		2,
+		"Cells",
+		IT_WEAPON,
+		WEAP_BOOMER,
+		NULL,
+		0,
+/* precache */ "weapons/rg_hum.wav weapons/rippfire.wav"
+	},
+// END 14-APR-98
+
+
+/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_railgun", 
+		Pickup_Weapon,
+		// RAFAEL
+		Use_Weapon2,
+		Drop_Weapon,
+		Weapon_Railgun,
+		"misc/w_pkup.wav",
+		"models/weapons/g_rail/tris.md2", EF_ROTATE,
+		"models/weapons/v_rail/tris.md2",
+/* icon */		"w_railgun",
+/* pickup */	"Railgun",
+		0,
+		1,
+		"Slugs",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_RAILGUN,
+		NULL,
+		0,
+/* precache */ "weapons/rg_hum.wav"
+	},
+
+// RAFAEL 14-APR-98
+/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+
+	{
+		"weapon_phalanx",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_Phalanx,
+		"misc/w_pkup.wav",
+		"models/weapons/g_shotx/tris.md2", EF_ROTATE,
+		"models/weapons/v_shotx/tris.md2",
+/* icon */	"w_phallanx",
+/* pickup */ "Phalanx",
+		0,
+		1,
+		"Mag Slug",
+		IT_WEAPON,
+		WEAP_PHALANX,
+		NULL,
+		0,
+/* precache */ "weapons/plasshot.wav"
+	},
+
+
+
+/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"weapon_bfg",
+		Pickup_Weapon,
+		Use_Weapon,
+		Drop_Weapon,
+		Weapon_BFG,
+		"misc/w_pkup.wav",
+		"models/weapons/g_bfg/tris.md2", EF_ROTATE,
+		"models/weapons/v_bfg/tris.md2",
+/* icon */		"w_bfg",
+/* pickup */	"BFG10K",
+		0,
+		50,
+		"Cells",
+		IT_WEAPON|IT_STAY_COOP,
+		WEAP_BFG,
+		NULL,
+		0,
+/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"
+	},
+
+	//
+	// AMMO ITEMS
+	//
+
+/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_shells",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/shells/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_shells",
+/* pickup */	"Shells",
+/* width */		3,
+		10,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_SHELLS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_bullets",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/bullets/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_bullets",
+/* pickup */	"Bullets",
+/* width */		3,
+		50,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_BULLETS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_cells",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/cells/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_cells",
+/* pickup */	"Cells",
+/* width */		3,
+		50,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_CELLS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_rockets",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/rockets/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_rockets",
+/* pickup */	"Rockets",
+/* width */		3,
+		5,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_ROCKETS,
+/* precache */ ""
+	},
+
+/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_slugs",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/items/ammo/slugs/medium/tris.md2", 0,
+		NULL,
+/* icon */		"a_slugs",
+/* pickup */	"Slugs",
+/* width */		3,
+		10,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_SLUGS,
+/* precache */ ""
+	},
+
+
+/*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"ammo_magslug",
+		Pickup_Ammo,
+		NULL,
+		Drop_Ammo,
+		NULL,
+		"misc/am_pkup.wav",
+		"models/objects/ammo/tris.md2", 0,
+		NULL,
+/* icon */		"a_mslugs",
+/* pickup */	"Mag Slug",
+/* width */		3,
+		10,
+		NULL,
+		IT_AMMO,
+		0,
+		NULL,
+		AMMO_MAGSLUG,
+/* precache */ ""
+	},
+
+	//
+	// POWERUP ITEMS
+	//
+/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_quad", 
+		Pickup_Powerup,
+		Use_Quad,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/quaddama/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_quad",
+/* pickup */	"Quad Damage",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav"
+	},
+
+/*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_quadfire", 
+		Pickup_Powerup,
+		Use_QuadFire,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/quadfire/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_quadfire",
+
+/* pickup */	"DualFire Damage",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav"
+	},
+
+	
+/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_invulnerability",
+		Pickup_Powerup,
+		Use_Invulnerability,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/invulner/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_invulnerability",
+/* pickup */	"Invulnerability",
+/* width */		2,
+		300,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav"
+	},
+
+/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_silencer",
+		Pickup_Powerup,
+		Use_Silencer,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/silencer/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_silencer",
+/* pickup */	"Silencer",
+/* width */		2,
+		60,
+		NULL,
+		IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_breather",
+		Pickup_Powerup,
+		Use_Breather,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/breather/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_rebreather",
+/* pickup */	"Rebreather",
+/* width */		2,
+		60,
+		NULL,
+		IT_STAY_COOP|IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/airout.wav"
+	},
+
+/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_enviro",
+		Pickup_Powerup,
+		Use_Envirosuit,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/enviro/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_envirosuit",
+/* pickup */	"Environment Suit",
+/* width */		2,
+		60,
+		NULL,
+		IT_STAY_COOP|IT_POWERUP,
+		0,
+		NULL,
+		0,
+/* precache */ "items/airout.wav"
+	},
+
+/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16)
+Special item that gives +2 to maximum health
+*/
+	{
+		"item_ancient_head",
+		Pickup_AncientHead,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/c_head/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_fixme",
+/* pickup */	"Ancient Head",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16)
+gives +1 to maximum health
+*/
+	{
+		"item_adrenaline",
+		Pickup_Adrenaline,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/adrenal/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_adrenaline",
+/* pickup */	"Adrenaline",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_bandolier",
+		Pickup_Bandolier,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/band/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"p_bandolier",
+/* pickup */	"Bandolier",
+/* width */		2,
+		60,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+	{
+		"item_pack",
+		Pickup_Pack,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		"models/items/pack/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_pack",
+/* pickup */	"Ammo Pack",
+/* width */		2,
+		180,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+	//
+	// KEYS
+	//
+/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for computer centers
+*/
+	{
+		"key_data_cd",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/data_cd/tris.md2", EF_ROTATE,
+		NULL,
+		"k_datacd",
+		"Data CD",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH
+warehouse circuits
+*/
+	{
+		"key_power_cube",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/power/tris.md2", EF_ROTATE,
+		NULL,
+		"k_powercube",
+		"Power Cube",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for the entrance of jail3
+*/
+	{
+		"key_pyramid",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/pyramid/tris.md2", EF_ROTATE,
+		NULL,
+		"k_pyramid",
+		"Pyramid Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16)
+key for the city computer
+*/
+	{
+		"key_data_spinner",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/spinner/tris.md2", EF_ROTATE,
+		NULL,
+		"k_dataspin",
+		"Data Spinner",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16)
+security pass for the security level
+*/
+	{
+		"key_pass",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/pass/tris.md2", EF_ROTATE,
+		NULL,
+		"k_security",
+		"Security Pass",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+normal door key - blue
+*/
+	{
+		"key_blue_key",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/key/tris.md2", EF_ROTATE,
+		NULL,
+		"k_bluekey",
+		"Blue Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+normal door key - red
+*/
+	{
+		"key_red_key",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/red_key/tris.md2", EF_ROTATE,
+		NULL,
+		"k_redkey",
+		"Red Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+
+// RAFAEL
+/*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16)
+normal door key - blue
+*/
+	{
+		"key_green_key",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/green_key/tris.md2", EF_ROTATE,
+		NULL,
+		"k_green",
+		"Green Key",
+		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16)
+tank commander's head
+*/
+	{
+		"key_commander_head",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/monsters/commandr/head/tris.md2", EF_GIB,
+		NULL,
+/* icon */		"k_comhead",
+/* pickup */	"Commander's Head",
+/* width */		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16)
+tank commander's head
+*/
+	{
+		"key_airstrike_target",
+		Pickup_Key,
+		NULL,
+		Drop_General,
+		NULL,
+		"items/pkup.wav",
+		"models/items/keys/target/tris.md2", EF_ROTATE,
+		NULL,
+/* icon */		"i_airstrike",
+/* pickup */	"Airstrike Marker",
+/* width */		2,
+		0,
+		NULL,
+		IT_STAY_COOP|IT_KEY,
+		0,
+		NULL,
+		0,
+/* precache */ ""
+	},
+
+	{
+		NULL,
+		Pickup_Health,
+		NULL,
+		NULL,
+		NULL,
+		"items/pkup.wav",
+		NULL, 0,
+		NULL,
+/* icon */		"i_health",
+/* pickup */	"Health",
+/* width */		3,
+		0,
+		NULL,
+		0,
+		0,
+		NULL,
+		0,
+/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"
+	},
+
+	// end of list marker
+	{NULL}
+};
+
+
+/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/medium/tris.md2";
+	self->count = 10;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/n_health.wav");
+}
+
+/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_small (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/stimpack/tris.md2";
+	self->count = 2;
+	SpawnItem (self, FindItem ("Health"));
+	self->style = HEALTH_IGNORE_MAX;
+	gi.soundindex ("items/s_health.wav");
+}
+
+/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_large (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/healing/large/tris.md2";
+	self->count = 25;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/l_health.wav");
+}
+
+/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16)
+*/
+void SP_item_health_mega (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/items/mega_h/tris.md2";
+	self->count = 100;
+	SpawnItem (self, FindItem ("Health"));
+	gi.soundindex ("items/m_health.wav");
+	self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED;
+}
+
+// RAFAEL
+void SP_item_foodcube (edict_t *self)
+{
+	if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) )
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->model = "models/objects/trapfx/tris.md2";
+	SpawnItem (self, FindItem ("Health"));
+	self->spawnflags |= DROPPED_ITEM;
+	self->style = HEALTH_IGNORE_MAX;
+	gi.soundindex ("items/s_health.wav");
+	self->classname = "foodcube";
+}
+
+
+void InitItems (void)
+{
+	game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1;
+}
+
+
+
+/*
+===============
+SetItemNames
+
+Called by worldspawn
+===============
+*/
+void SetItemNames (void)
+{
+	int		i;
+	gitem_t	*it;
+
+	for (i=0 ; i<game.num_items ; i++)
+	{
+		it = &itemlist[i];
+		gi.configstring (CS_ITEMS+i, it->pickup_name);
+	}
+
+	jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor"));
+	combat_armor_index = ITEM_INDEX(FindItem("Combat Armor"));
+	body_armor_index   = ITEM_INDEX(FindItem("Body Armor"));
+	power_screen_index = ITEM_INDEX(FindItem("Power Screen"));
+	power_shield_index = ITEM_INDEX(FindItem("Power Shield"));
+}
--- /dev/null
+++ b/xatrix/g_main.c
@@ -1,0 +1,364 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+game_locals_t	game;
+level_locals_t	level;
+game_import_t	gi;
+game_export_t	globals;
+spawn_temp_t	st;
+
+int	sm_meat_index;
+int	snd_fry;
+int meansOfDeath;
+
+edict_t		*g_edicts;
+
+cvar_t	*deathmatch;
+cvar_t	*coop;
+cvar_t	*dmflags;
+cvar_t	*skill;
+cvar_t	*fraglimit;
+cvar_t	*timelimit;
+cvar_t	*password;
+cvar_t	*spectator_password;
+cvar_t	*maxclients;
+cvar_t	*maxspectators;
+cvar_t	*maxentities;
+cvar_t	*g_select_empty;
+cvar_t	*dedicated;
+
+cvar_t	*filterban;
+
+cvar_t	*sv_maxvelocity;
+cvar_t	*sv_gravity;
+
+cvar_t	*sv_rollspeed;
+cvar_t	*sv_rollangle;
+cvar_t	*gun_x;
+cvar_t	*gun_y;
+cvar_t	*gun_z;
+
+cvar_t	*run_pitch;
+cvar_t	*run_roll;
+cvar_t	*bob_up;
+cvar_t	*bob_pitch;
+cvar_t	*bob_roll;
+
+cvar_t	*sv_cheats;
+
+cvar_t	*flood_msgs;
+cvar_t	*flood_persecond;
+cvar_t	*flood_waitdelay;
+
+cvar_t	*sv_maplist;
+
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint);
+void ClientThink (edict_t *ent, usercmd_t *cmd);
+qboolean ClientConnect (edict_t *ent, char *userinfo);
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+void ClientDisconnect (edict_t *ent);
+void ClientBegin (edict_t *ent);
+void ClientCommand (edict_t *ent);
+void RunEntity (edict_t *ent);
+void WriteGame (char *filename, qboolean autosave);
+void ReadGame (char *filename);
+void WriteLevel (char *filename);
+void ReadLevel (char *filename);
+void InitGame (void);
+void G_RunFrame (void);
+
+
+//===================================================================
+
+
+void ShutdownGame (void)
+{
+	gi.dprintf ("==== ShutdownGame ====\n");
+
+	gi.FreeTags (TAG_LEVEL);
+	gi.FreeTags (TAG_GAME);
+}
+
+
+/*
+=================
+GetGameAPI
+
+Returns a pointer to the structure with all entry points
+and global variables
+=================
+*/
+game_export_t *GetGameAPI (game_import_t *import)
+{
+	gi = *import;
+
+	globals.apiversion = GAME_API_VERSION;
+	globals.Init = InitGame;
+	globals.Shutdown = ShutdownGame;
+	globals.SpawnEntities = SpawnEntities;
+
+	globals.WriteGame = WriteGame;
+	globals.ReadGame = ReadGame;
+	globals.WriteLevel = WriteLevel;
+	globals.ReadLevel = ReadLevel;
+
+	globals.ClientThink = ClientThink;
+	globals.ClientConnect = ClientConnect;
+	globals.ClientUserinfoChanged = ClientUserinfoChanged;
+	globals.ClientDisconnect = ClientDisconnect;
+	globals.ClientBegin = ClientBegin;
+	globals.ClientCommand = ClientCommand;
+
+	globals.RunFrame = G_RunFrame;
+
+	globals.ServerCommand = ServerCommand;
+
+	globals.edict_size = sizeof(edict_t);
+
+	return &globals;
+}
+
+/*
+=================
+ClientEndServerFrames
+=================
+*/
+void ClientEndServerFrames (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	// calc the player views now that all pushing
+	// and damage has been added
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse || !ent->client)
+			continue;
+		ClientEndServerFrame (ent);
+	}
+
+}
+
+/*
+=================
+CreateTargetChangeLevel
+
+Returns the created target changelevel
+=================
+*/
+edict_t *CreateTargetChangeLevel(char *map)
+{
+	edict_t *ent;
+
+	ent = G_Spawn ();
+	ent->classname = "target_changelevel";
+	Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map);
+	ent->map = level.nextmap;
+	return ent;
+}
+
+/*
+=================
+EndDMLevel
+
+The timelimit or fraglimit has been exceeded
+=================
+*/
+void EndDMLevel (void)
+{
+	edict_t		*ent;
+	char *s, *t, *f;
+	static const char *seps = " ,\n\r";
+
+	// stay on same level flag
+	if ((int)dmflags->value & DF_SAME_LEVEL)
+	{
+		BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+		return;
+	}
+
+	// see if it's in the map list
+	if (*sv_maplist->string) {
+		s = strdup(sv_maplist->string);
+		f = NULL;
+		t = strtok(s, seps);
+		while (t != NULL) {
+			if (cistrcmp(t, level.mapname) == 0) {
+				// it's in the list, go to the next one
+				t = strtok(NULL, seps);
+				if (t == NULL) { // end of list, go to first one
+					if (f == NULL) // there isn't a first one, same level
+						BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+					else
+						BeginIntermission (CreateTargetChangeLevel (f) );
+				} else
+					BeginIntermission (CreateTargetChangeLevel (t) );
+				free(s);
+				return;
+			}
+			if (!f)
+				f = t;
+			t = strtok(NULL, seps);
+		}
+		free(s);
+	}
+
+	if (level.nextmap[0]) // go to a specific map
+		BeginIntermission (CreateTargetChangeLevel (level.nextmap) );
+	else {	// search for a changelevel
+		ent = G_Find (NULL, FOFS(classname), "target_changelevel");
+		if (!ent)
+		{	// the map designer didn't include a changelevel,
+			// so create a fake ent that goes back to the same level
+			BeginIntermission (CreateTargetChangeLevel (level.mapname) );
+			return;
+		}
+		BeginIntermission (ent);
+	}
+}
+
+/*
+=================
+CheckDMRules
+=================
+*/
+void CheckDMRules (void)
+{
+	int			i;
+	gclient_t	*cl;
+
+	if (level.intermissiontime)
+		return;
+
+	if (!deathmatch->value)
+		return;
+
+	if (timelimit->value)
+	{
+		if (level.time >= timelimit->value*60)
+		{
+			gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
+			EndDMLevel ();
+			return;
+		}
+	}
+
+	if (fraglimit->value)
+	{
+		for (i=0 ; i<maxclients->value ; i++)
+		{
+			cl = game.clients + i;
+			if (!g_edicts[i+1].inuse)
+				continue;
+
+			if (cl->resp.score >= fraglimit->value)
+			{
+				gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n");
+				EndDMLevel ();
+				return;
+			}
+		}
+	}
+}
+
+
+/*
+=============
+ExitLevel
+=============
+*/
+void ExitLevel (void)
+{
+	int		i;
+	edict_t	*ent;
+	char	command [256];
+
+	Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap);
+	gi.AddCommandString (command);
+	level.changemap = NULL;
+	level.exitintermission = 0;
+	level.intermissiontime = 0;
+	ClientEndServerFrames ();
+
+	// clear some things before going to next level
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = g_edicts + 1 + i;
+		if (!ent->inuse)
+			continue;
+		if (ent->health > ent->client->pers.max_health)
+			ent->health = ent->client->pers.max_health;
+	}
+
+}
+
+/*
+================
+G_RunFrame
+
+Advances the world by 0.1 seconds
+================
+*/
+void G_RunFrame (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.framenum++;
+	level.time = level.framenum*FRAMETIME;
+
+	// choose a client for monsters to target this frame
+	AI_SetSightClient ();
+
+	// exit intermissions
+
+	if (level.exitintermission)
+	{
+		ExitLevel ();
+		return;
+	}
+
+	//
+	// treat each object in turn
+	// even the world gets a chance to think
+	//
+	ent = &g_edicts[0];
+	for (i=0 ; i<globals.num_edicts ; i++, ent++)
+	{
+		if (!ent->inuse)
+			continue;
+
+		level.current_entity = ent;
+
+		VectorCopy (ent->s.origin, ent->s.old_origin);
+
+		// if the ground entity moved, make sure we are still on it
+		if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount))
+		{
+			ent->groundentity = NULL;
+			if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) )
+			{
+				M_CheckGround (ent);
+			}
+		}
+
+		if (i > 0 && i <= maxclients->value)
+		{
+			ClientBeginServerFrame (ent);
+			continue;
+		}
+
+		G_RunEntity (ent);
+	}
+
+	// see if it is time to end a deathmatch
+	CheckDMRules ();
+
+	// build the playerstate_t structures for all players
+	ClientEndServerFrames ();
+}
+
--- /dev/null
+++ b/xatrix/g_misc.c
@@ -1,0 +1,2177 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+/*QUAKED func_group (0 0 0) ?
+Used to group brushes together just for editor convenience.
+*/
+
+//=====================================================
+
+void Use_Areaportal (edict_t *ent, edict_t *, edict_t *)
+{
+	ent->count ^= 1;		// toggle state
+//	gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count);
+	gi.SetAreaPortalState (ent->style, ent->count);
+}
+
+/*QUAKED func_areaportal (0 0 0) ?
+
+This is a non-visible object that divides the world into
+areas that are seperated when this portal is not activated.
+Usually enclosed in the middle of a door.
+*/
+void SP_func_areaportal (edict_t *ent)
+{
+	ent->use = Use_Areaportal;
+	ent->count = 0;		// always start closed;
+}
+
+//=====================================================
+
+
+/*
+=================
+Misc functions
+=================
+*/
+void VelocityForDamage (int damage, vec3_t v)
+{
+	v[0] = 100.0 * crandom();
+	v[1] = 100.0 * crandom();
+	v[2] = 200.0 + 100.0 * qrandom();
+
+	if (damage < 50)
+		VectorScale (v, 0.7, v);
+	else 
+		VectorScale (v, 1.2, v);
+}
+
+void ClipGibVelocity (edict_t *ent)
+{
+	if (ent->velocity[0] < -300)
+		ent->velocity[0] = -300;
+	else if (ent->velocity[0] > 300)
+		ent->velocity[0] = 300;
+	if (ent->velocity[1] < -300)
+		ent->velocity[1] = -300;
+	else if (ent->velocity[1] > 300)
+		ent->velocity[1] = 300;
+	if (ent->velocity[2] < 200)
+		ent->velocity[2] = 200;	// always some upwards
+	else if (ent->velocity[2] > 500)
+		ent->velocity[2] = 500;
+}
+
+
+/*
+=================
+gibs
+=================
+*/
+void gib_think (edict_t *self)
+{
+	self->s.frame++;
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->s.frame == 10)
+	{
+		self->think = G_FreeEdict;
+		self->nextthink = level.time + 8 + qrandom()*10;
+	}
+}
+
+void gib_touch (edict_t *self, edict_t *, cplane_t *plane, csurface_t *)
+{
+	vec3_t	normal_angles, right;
+
+	if (!self->groundentity)
+		return;
+
+	self->touch = NULL;
+
+	if (plane)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+		vectoangles (plane->normal, normal_angles);
+		AngleVectors (normal_angles, NULL, right, NULL);
+		vectoangles (right, self->s.angles);
+
+		if (self->s.modelindex == sm_meat_index)
+		{
+			self->s.frame++;
+			self->think = gib_think;
+			self->nextthink = level.time + FRAMETIME;
+		}
+	}
+}
+
+// RAFAEL 24-APR-98
+// removed acid damage
+/*
+// RAFAEL
+void gib_touchacid (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *)
+{
+	vec3_t normal_angles, right;
+
+	if (other->takedamage)
+	{
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+		G_FreeEdict (self);
+	}
+
+	if (!self->groundentity)
+		return;
+
+	if (plane)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0);
+
+		vectoangles (plane->normal, normal_angles);
+		AngleVectors (normal_angles, NULL, right, NULL);
+		vectoangles (right, self->s.angles);
+
+		if (self->s.modelindex == sm_meat_index)
+		{
+			self->s.frame++;
+			self->think = gib_think;
+			self->nextthink = level.time + FRAMETIME;
+		}
+	}
+}
+*/
+
+void gib_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowGib (edict_t *self, char *gibname, int damage, int type)
+{
+	edict_t *gib;
+	vec3_t	vd;
+	vec3_t	origin;
+	vec3_t	size;
+	float	vscale;
+
+	gib = G_Spawn();
+
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	gib->s.origin[0] = origin[0] + crandom() * size[0];
+	gib->s.origin[1] = origin[1] + crandom() * size[1];
+	gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+	gi.setmodel (gib, gibname);
+	gib->solid = SOLID_NOT;
+	gib->s.effects |= EF_GIB;
+	gib->flags |= FL_NO_KNOCKBACK;
+	gib->takedamage = DAMAGE_YES;
+	gib->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		gib->movetype = MOVETYPE_TOSS;
+		gib->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		gib->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, gib->velocity);
+	ClipGibVelocity (gib);
+	gib->avelocity[0] = qrandom()*600;
+	gib->avelocity[1] = qrandom()*600;
+	gib->avelocity[2] = qrandom()*600;
+
+	gib->think = G_FreeEdict;
+	gib->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (gib);
+}
+
+
+
+void ThrowHead (edict_t *self, char *gibname, int damage, int type)
+{
+	vec3_t	vd;
+	float	vscale;
+
+	self->s.skinnum = 0;
+	self->s.frame = 0;
+	VectorClear (self->mins);
+	VectorClear (self->maxs);
+
+	self->s.modelindex2 = 0;
+	gi.setmodel (self, gibname);
+	self->solid = SOLID_NOT;
+	self->s.effects |= EF_GIB;
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+	self->svflags &= ~SVF_MONSTER;
+	self->takedamage = DAMAGE_YES;
+	self->die = gib_die;
+
+	if (type == GIB_ORGANIC)
+	{
+		self->movetype = MOVETYPE_TOSS;
+		self->touch = gib_touch;
+		vscale = 0.5;
+	}
+	else
+	{
+		self->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, self->velocity);
+	ClipGibVelocity (self);
+
+	self->avelocity[YAW] = crandom()*600;
+
+	self->think = G_FreeEdict;
+	self->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (self);
+}
+
+
+// RAFAEL
+void ThrowGibACID (edict_t *self, char *gibname, int damage, int type)
+{
+	edict_t *gib;
+	vec3_t vd;
+	vec3_t origin;
+	vec3_t size;
+	float vscale;
+
+	gib = G_Spawn();
+
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	gib->s.origin[0] = origin[0] + crandom() * size[0];
+	gib->s.origin[1] = origin[1] + crandom() * size[1];
+	gib->s.origin[2] = origin[2] + crandom() * size[2];
+
+	// gi.setmodel (gib, gibname);
+	gib->s.modelindex = gi.modelindex (gibname);
+
+	gib->clipmask = MASK_SHOT;
+	gib->solid = SOLID_BBOX;
+ 
+	gib->s.effects |= EF_GREENGIB;
+	// note to self check this
+	gib->s.renderfx |= RF_FULLBRIGHT;
+	gib->flags |= FL_NO_KNOCKBACK;
+	gib->takedamage = DAMAGE_YES;
+	gib->die = gib_die;
+	gib->dmg = 2;
+
+	if (type == GIB_ORGANIC)
+	{
+		gib->movetype = MOVETYPE_TOSS;
+		// RAFAEL 24-APR-98
+		// removed acid damage
+		//gib->touch = gib_touchacid;
+		vscale = 3.0;
+	}
+	else
+	{
+		gib->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, gib->velocity);
+	ClipGibVelocity (gib);
+	gib->avelocity[0] = qrandom()*600;
+	gib->avelocity[1] = qrandom()*600;
+	gib->avelocity[2] = qrandom()*600;
+
+	gib->think = G_FreeEdict;
+	gib->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (gib);
+}
+
+// RAFAEL
+void ThrowHeadACID (edict_t *self, char *gibname, int damage, int type)
+{
+	vec3_t vd;
+	float vscale;
+
+	self->s.skinnum = 0;
+	self->s.frame = 0;
+	VectorClear (self->mins);
+	VectorClear (self->maxs);
+
+	self->s.modelindex2 = 0;
+	gi.setmodel (self, gibname);
+ 
+	self->clipmask = MASK_SHOT;
+	self->solid = SOLID_BBOX;
+  
+	self->s.effects |= EF_GREENGIB;
+	self->s.effects &= ~EF_FLIES;
+	self->s.effects |= RF_FULLBRIGHT;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+	self->svflags &= ~SVF_MONSTER;
+	self->takedamage = DAMAGE_YES;
+	self->die = gib_die;
+	self->dmg = 2;
+
+	if (type == GIB_ORGANIC)
+	{
+		self->movetype = MOVETYPE_TOSS;
+		// RAFAEL 24-APR-98
+		// removed acid damage
+		// self->touch = gib_touchacid;
+		vscale = 0.5;
+	}
+	else
+	{
+		self->movetype = MOVETYPE_BOUNCE;
+		vscale = 1.0;
+	}
+
+	VelocityForDamage (damage, vd);
+	VectorMA (self->velocity, vscale, vd, self->velocity);
+	ClipGibVelocity (self);
+
+	self->avelocity[YAW] = crandom()*600;
+
+	self->think = G_FreeEdict;
+	self->nextthink = level.time + 10 + qrandom()*10;
+
+	gi.linkentity (self);
+}
+
+
+void ThrowClientHead (edict_t *self, int damage)
+{
+	vec3_t	vd;
+	char	*gibname;
+
+	if (rand()&1)
+	{
+		gibname = "models/objects/gibs/head2/tris.md2";
+		self->s.skinnum = 1;		// second skin is player
+	}
+	else
+	{
+		gibname = "models/objects/gibs/skull/tris.md2";
+		self->s.skinnum = 0;
+	}
+
+	self->s.origin[2] += 32;
+	self->s.frame = 0;
+	gi.setmodel (self, gibname);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 16);
+
+	self->takedamage = DAMAGE_NO;
+	self->solid = SOLID_NOT;
+	self->s.effects = EF_GIB;
+	self->s.sound = 0;
+	self->flags |= FL_NO_KNOCKBACK;
+
+	self->movetype = MOVETYPE_BOUNCE;
+	VelocityForDamage (damage, vd);
+	VectorAdd (self->velocity, vd, self->velocity);
+
+	if (self->client)	// bodies in the queue don't have a client anymore
+	{
+		self->client->anim_priority = ANIM_DEATH;
+		self->client->anim_end = self->s.frame;
+	}
+	else
+	{
+		self->think = NULL;
+		self->nextthink = 0;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*
+=================
+debris
+=================
+*/
+void debris_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	G_FreeEdict (self);
+}
+
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin)
+{
+	edict_t	*chunk;
+	vec3_t	v;
+
+	chunk = G_Spawn();
+	VectorCopy (origin, chunk->s.origin);
+	gi.setmodel (chunk, modelname);
+	v[0] = 100 * crandom();
+	v[1] = 100 * crandom();
+	v[2] = 100 + 100 * crandom();
+	VectorMA (self->velocity, speed, v, chunk->velocity);
+	chunk->movetype = MOVETYPE_BOUNCE;
+	chunk->solid = SOLID_NOT;
+	chunk->avelocity[0] = qrandom()*600;
+	chunk->avelocity[1] = qrandom()*600;
+	chunk->avelocity[2] = qrandom()*600;
+	chunk->think = G_FreeEdict;
+	chunk->nextthink = level.time + 5 + qrandom()*5;
+	chunk->s.frame = 0;
+	chunk->flags = 0;
+	chunk->classname = "debris";
+	chunk->takedamage = DAMAGE_YES;
+	chunk->die = debris_die;
+	gi.linkentity (chunk);
+}
+
+
+void BecomeExplosion1 (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+void BecomeExplosion2 (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION2);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+
+/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT
+Target: next path corner
+Pathtarget: gets used when an entity that has
+	this path_corner targeted touches it
+*/
+
+void path_corner_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t		v;
+	edict_t		*next;
+
+	if (other->movetarget != self)
+		return;
+	
+	if (other->enemy)
+		return;
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		G_UseTargets (self, other);
+		self->target = savetarget;
+	}
+
+	if (self->target)
+		next = G_PickTarget(self->target);
+	else
+		next = NULL;
+
+	if ((next) && (next->spawnflags & 1))
+	{
+		VectorCopy (next->s.origin, v);
+		v[2] += next->mins[2];
+		v[2] -= other->mins[2];
+		VectorCopy (v, other->s.origin);
+		next = G_PickTarget(next->target);
+		other->s.event = EV_OTHER_TELEPORT;
+	}
+
+	other->goalentity = other->movetarget = next;
+
+	if (self->wait)
+	{
+		other->monsterinfo.pausetime = level.time + self->wait;
+		other->monsterinfo.stand (other);
+		return;
+	}
+
+	if (!other->movetarget)
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.stand (other);
+	}
+	else
+	{
+		VectorSubtract (other->goalentity->s.origin, other->s.origin, v);
+		other->ideal_yaw = vectoyaw (v);
+	}
+}
+
+void SP_path_corner (edict_t *self)
+{
+	if (!self->targetname)
+	{
+		gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = path_corner_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags |= SVF_NOCLIENT;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold
+Makes this the target of a monster and it will head here
+when first activated before going after the activator.  If
+hold is selected, it will stay here.
+*/
+void point_combat_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t	*activator;
+
+	if (other->movetarget != self)
+		return;
+
+	if (self->target)
+	{
+		other->target = self->target;
+		other->goalentity = other->movetarget = G_PickTarget(other->target);
+		if (!other->goalentity)
+		{
+			gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target);
+			other->movetarget = self;
+		}
+		self->target = NULL;
+	}
+	else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY)))
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.aiflags |= AI_STAND_GROUND;
+		other->monsterinfo.stand (other);
+	}
+
+	if (other->movetarget == self)
+	{
+		other->target = NULL;
+		other->movetarget = NULL;
+		other->goalentity = other->enemy;
+		other->monsterinfo.aiflags &= ~AI_COMBAT_POINT;
+	}
+
+	if (self->pathtarget)
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		if (other->enemy && other->enemy->client)
+			activator = other->enemy;
+		else if (other->oldenemy && other->oldenemy->client)
+			activator = other->oldenemy;
+		else if (other->activator && other->activator->client)
+			activator = other->activator;
+		else
+			activator = other;
+		G_UseTargets (self, activator);
+		self->target = savetarget;
+	}
+}
+
+void SP_point_combat (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	self->solid = SOLID_TRIGGER;
+	self->touch = point_combat_touch;
+	VectorSet (self->mins, -8, -8, -16);
+	VectorSet (self->maxs, 8, 8, 16);
+	self->svflags = SVF_NOCLIENT;
+	gi.linkentity (self);
+};
+
+
+/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8)
+Just for the debugging level.  Don't use
+*/
+void TH_viewthing(edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 7;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_viewthing(edict_t *ent)
+{
+	gi.dprintf ("viewthing spawned\n");
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.renderfx = RF_FRAMELERP;
+	VectorSet (ent->mins, -16, -16, -24);
+	VectorSet (ent->maxs, 16, 16, 32);
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	gi.linkentity (ent);
+	ent->nextthink = level.time + 0.5;
+	ent->think = TH_viewthing;
+	return;
+}
+
+
+/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for spotlights, etc.
+*/
+void SP_info_null (edict_t *self)
+{
+	G_FreeEdict (self);
+};
+
+
+/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
+Used as a positional target for lightning.
+*/
+void SP_info_notnull (edict_t *self)
+{
+	VectorCopy (self->s.origin, self->absmin);
+	VectorCopy (self->s.origin, self->absmax);
+};
+
+
+/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF
+Non-displayed light.
+Default light value is 300.
+Default style is 0.
+If targeted, will toggle between on and off.
+Default _cone value is 10 (used to set size of light for spotlights)
+*/
+
+#define START_OFF	1
+
+static void light_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->spawnflags & START_OFF)
+	{
+		gi.configstring (CS_LIGHTS+self->style, "m");
+		self->spawnflags &= ~START_OFF;
+	}
+	else
+	{
+		gi.configstring (CS_LIGHTS+self->style, "a");
+		self->spawnflags |= START_OFF;
+	}
+}
+
+void SP_light (edict_t *self)
+{
+	// no targeted lights in deathmatch, because they cause global messages
+	if (!self->targetname || deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->style >= 32)
+	{
+		self->use = light_use;
+		if (self->spawnflags & START_OFF)
+			gi.configstring (CS_LIGHTS+self->style, "a");
+		else
+			gi.configstring (CS_LIGHTS+self->style, "m");
+	}
+}
+
+
+/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST
+This is just a solid wall if not inhibited
+
+TRIGGER_SPAWN	the wall will not be present until triggered
+				it will then blink in to existance; it will
+				kill anything that was in it's way
+
+TOGGLE			only valid for TRIGGER_SPAWN walls
+				this allows the wall to be turned on and off
+
+START_ON		only valid for TRIGGER_SPAWN walls
+				the wall will initially be present
+*/
+
+void func_wall_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+	{
+		self->solid = SOLID_BSP;
+		self->svflags &= ~SVF_NOCLIENT;
+		KillBox (self);
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+void SP_func_wall (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 8)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 16)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	// just a wall
+	if ((self->spawnflags & 7) == 0)
+	{
+		self->solid = SOLID_BSP;
+		gi.linkentity (self);
+		return;
+	}
+
+	// it must be TRIGGER_SPAWN
+	if (!(self->spawnflags & 1))
+	{
+//		gi.dprintf("func_wall missing TRIGGER_SPAWN\n");
+		self->spawnflags |= 1;
+	}
+
+	// yell if the spawnflags are odd
+	if (self->spawnflags & 4)
+	{
+		if (!(self->spawnflags & 2))
+		{
+			gi.dprintf("func_wall START_ON without TOGGLE\n");
+			self->spawnflags |= 2;
+		}
+	}
+
+	self->use = func_wall_use;
+	if (self->spawnflags & 4)
+	{
+		self->solid = SOLID_BSP;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->svflags |= SVF_NOCLIENT;
+	}
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST
+This is solid bmodel that will fall if it's support it removed.
+*/
+
+void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *)
+{
+	// only squash thing we fall on top of
+	if (!plane)
+		return;
+	if (plane->normal[2] < 1.0)
+		return;
+	if (other->takedamage == DAMAGE_NO)
+		return;
+	T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);
+}
+
+void func_object_release (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->touch = func_object_touch;
+}
+
+void func_object_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	func_object_release (self);
+}
+
+void SP_func_object (edict_t *self)
+{
+	gi.setmodel (self, self->model);
+
+	self->mins[0] += 1;
+	self->mins[1] += 1;
+	self->mins[2] += 1;
+	self->maxs[0] -= 1;
+	self->maxs[1] -= 1;
+	self->maxs[2] -= 1;
+
+	if (!self->dmg)
+		self->dmg = 100;
+
+	if (self->spawnflags == 0)
+	{
+		self->solid = SOLID_BSP;
+		self->movetype = MOVETYPE_PUSH;
+		self->think = func_object_release;
+		self->nextthink = level.time + 2 * FRAMETIME;
+	}
+	else
+	{
+		self->solid = SOLID_NOT;
+		self->movetype = MOVETYPE_PUSH;
+		self->use = func_object_use;
+		self->svflags |= SVF_NOCLIENT;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	self->clipmask = MASK_MONSTERSOLID;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST
+Any brush that you want to explode or break apart.  If you want an
+ex0plosion, set dmg and it will do a radius explosion of that amount
+at the center of the bursh.
+
+If targeted it will not be shootable.
+
+health defaults to 100.
+
+mass defaults to 75.  This determines how much debris is emitted when
+it explodes.  You get one large chunk per 100 of mass (up to 8) and
+one small chunk per 25 of mass (up to 16).  So 800 gives the most.
+*/
+void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int, vec3_t)
+{
+	vec3_t	origin;
+	vec3_t	chunkorigin;
+	vec3_t	size;
+	int		count;
+	int		mass;
+
+	// bmodel origins are (0 0 0), we need to adjust that here
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	VectorCopy (origin, self->s.origin);
+
+	self->takedamage = DAMAGE_NO;
+
+	if (self->dmg)
+		T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity);
+	VectorNormalize (self->velocity);
+	VectorScale (self->velocity, 150, self->velocity);
+
+	// start chunks towards the center
+	VectorScale (size, 0.5, size);
+
+	mass = self->mass;
+	if (!mass)
+		mass = 75;
+
+	// big chunks
+	if (mass >= 100)
+	{
+		count = mass / 100;
+		if (count > 8)
+			count = 8;
+		while(count--)
+		{
+			chunkorigin[0] = origin[0] + crandom() * size[0];
+			chunkorigin[1] = origin[1] + crandom() * size[1];
+			chunkorigin[2] = origin[2] + crandom() * size[2];
+			ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin);
+		}
+	}
+
+	// small chunks
+	count = mass / 25;
+	if (count > 16)
+		count = 16;
+	while(count--)
+	{
+		chunkorigin[0] = origin[0] + crandom() * size[0];
+		chunkorigin[1] = origin[1] + crandom() * size[1];
+		chunkorigin[2] = origin[2] + crandom() * size[2];
+		ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin);
+	}
+
+	G_UseTargets (self, attacker);
+
+	if (self->dmg)
+		BecomeExplosion1 (self);
+	else
+		G_FreeEdict (self);
+}
+
+void func_explosive_use(edict_t *self, edict_t *other, edict_t *)
+{
+	func_explosive_explode (self, self, other, self->health, vec3_origin);
+}
+
+void func_explosive_spawn (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_BSP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = NULL;
+	KillBox (self);
+	gi.linkentity (self);
+}
+
+void SP_func_explosive (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+
+	gi.setmodel (self, self->model);
+
+	if (self->spawnflags & 1)
+	{
+		self->svflags |= SVF_NOCLIENT;
+		self->solid = SOLID_NOT;
+		self->use = func_explosive_spawn;
+	}
+	else
+	{
+		self->solid = SOLID_BSP;
+		if (self->targetname)
+			self->use = func_explosive_use;
+	}
+
+	if (self->spawnflags & 2)
+		self->s.effects |= EF_ANIM_ALL;
+	if (self->spawnflags & 4)
+		self->s.effects |= EF_ANIM_ALLFAST;
+
+	if (self->use != func_explosive_use)
+	{
+		if (!self->health)
+			self->health = 100;
+		self->die = func_explosive_explode;
+		self->takedamage = DAMAGE_YES;
+	}
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40)
+Large exploding box.  You can override its mass (100),
+health (80), and dmg (150).
+*/
+
+void barrel_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+
+{
+	float	ratio;
+	vec3_t	v;
+
+	if ((!other->groundentity) || (other->groundentity == self))
+		return;
+
+	ratio = (float)other->mass / (float)self->mass;
+	VectorSubtract (self->s.origin, other->s.origin, v);
+	M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME);
+}
+
+void barrel_explode (edict_t *self)
+{
+	vec3_t	org;
+	float	spd;
+	vec3_t	save;
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL);
+
+	VectorCopy (self->s.origin, save);
+	VectorMA (self->absmin, 0.5, self->size, self->s.origin);
+
+	// a few big chunks
+	spd = 1.5 * (float)self->dmg / 200.0;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org);
+
+	// bottom corners
+	spd = 1.75 * (float)self->dmg / 200.0;
+	VectorCopy (self->absmin, org);
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+	VectorCopy (self->absmin, org);
+	org[0] += self->size[0];
+	org[1] += self->size[1];
+	ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org);
+
+	// a bunch of little chunks
+	spd = 2 * self->dmg / 200;
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+	org[0] = self->s.origin[0] + crandom() * self->size[0];
+	org[1] = self->s.origin[1] + crandom() * self->size[1];
+	org[2] = self->s.origin[2] + crandom() * self->size[2];
+	ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org);
+
+	VectorCopy (save, self->s.origin);
+	if (self->groundentity)
+		BecomeExplosion2 (self);
+	else
+		BecomeExplosion1 (self);
+}
+
+void barrel_delay (edict_t *self, edict_t *, edict_t *attacker, int, vec3_t)
+{
+	self->takedamage = DAMAGE_NO;
+	self->nextthink = level.time + 2 * FRAMETIME;
+	self->think = barrel_explode;
+	self->activator = attacker;
+}
+
+void SP_misc_explobox (edict_t *self)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (self);
+		return;
+	}
+
+	gi.modelindex ("models/objects/debris1/tris.md2");
+	gi.modelindex ("models/objects/debris2/tris.md2");
+	gi.modelindex ("models/objects/debris3/tris.md2");
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+
+	self->model = "models/objects/barrels/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 40);
+
+	if (!self->mass)
+		self->mass = 400;
+	if (!self->health)
+		self->health = 10;
+	if (!self->dmg)
+		self->dmg = 150;
+
+	self->die = barrel_delay;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.aiflags = AI_NOSTEP;
+
+	self->touch = barrel_touch;
+
+	self->think = M_droptofloor;
+	self->nextthink = level.time + 2 * FRAMETIME;
+
+	gi.linkentity (self);
+}
+
+
+//
+// miscellaneous specialty items
+//
+
+/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8)
+*/
+
+void misc_blackhole_use (edict_t *ent, edict_t *, edict_t *)
+{
+	/*
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	*/
+	G_FreeEdict (ent);
+}
+
+void misc_blackhole_think (edict_t *self)
+{
+	if (++self->s.frame < 19)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 0;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_blackhole (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 8);
+	ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2");
+	ent->s.renderfx = RF_TRANSLUCENT;
+	ent->use = misc_blackhole_use;
+	ent->think = misc_blackhole_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32)
+*/
+
+void misc_eastertank_think (edict_t *self)
+{
+	if (++self->s.frame < 293)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 254;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_eastertank (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, -16);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
+	ent->s.frame = 254;
+	ent->think = misc_eastertank_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick_think (edict_t *self)
+{
+	if (++self->s.frame < 247)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 208;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 208;
+	ent->think = misc_easterchick_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+*/
+
+
+void misc_easterchick2_think (edict_t *self)
+{
+	if (++self->s.frame < 287)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 248;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void SP_misc_easterchick2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -32, -32, 0);
+	VectorSet (ent->maxs, 32, 32, 32);
+	ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	ent->s.frame = 248;
+	ent->think = misc_easterchick2_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48)
+Not really a monster, this is the Tank Commander's decapitated body.
+There should be a item_commander_head that has this as it's target.
+*/
+
+void commander_body_think (edict_t *self)
+{
+	if (++self->s.frame < 24)
+		self->nextthink = level.time + FRAMETIME;
+	else
+		self->nextthink = 0;
+
+	if (self->s.frame == 22)
+		gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->think = commander_body_think;
+	self->nextthink = level.time + FRAMETIME;
+	gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0);
+}
+
+void commander_body_drop (edict_t *self)
+{
+	self->movetype = MOVETYPE_TOSS;
+	self->s.origin[2] += 2;
+}
+
+void SP_monster_commander_body (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_BBOX;
+	self->model = "models/monsters/commandr/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	VectorSet (self->mins, -32, -32, 0);
+	VectorSet (self->maxs, 32, 32, 48);
+	self->use = commander_body_use;
+	self->takedamage = DAMAGE_YES;
+	self->flags = FL_GODMODE;
+	self->s.renderfx |= RF_FRAMELERP;
+	gi.linkentity (self);
+
+	gi.soundindex ("tank/thud.wav");
+	gi.soundindex ("tank/pain.wav");
+
+	self->think = commander_body_drop;
+	self->nextthink = level.time + 5 * FRAMETIME;
+}
+
+
+/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4)
+The origin is the bottom of the banner.
+The banner is 128 tall.
+*/
+void misc_banner_think (edict_t *ent)
+{
+	ent->s.frame = (ent->s.frame + 1) % 16;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_banner (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2");
+	ent->s.frame = rand() % 16;
+	gi.linkentity (ent);
+
+	ent->think = misc_banner_think;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED
+This is the dead player model. Comes in 6 exciting different poses!
+*/
+void misc_deadsoldier_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health > -80)
+		return;
+
+	gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+	for (n= 0; n < 4; n++)
+		ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+	ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+}
+
+void SP_misc_deadsoldier (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2");
+
+	// Defaults to frame 0
+	if (ent->spawnflags & 2)
+		ent->s.frame = 1;
+	else if (ent->spawnflags & 4)
+		ent->s.frame = 2;
+	else if (ent->spawnflags & 8)
+		ent->s.frame = 3;
+	else if (ent->spawnflags & 16)
+		ent->s.frame = 4;
+	else if (ent->spawnflags & 32)
+		ent->s.frame = 5;
+	else
+		ent->s.frame = 0;
+
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 16);
+	ent->deadflag = DEAD_DEAD;
+	ent->takedamage = DAMAGE_YES;
+	ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER;
+	ent->die = misc_deadsoldier_die;
+	ent->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32)
+This is the Viper for the flyby bombing.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast the Viper should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_viper_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_viper (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_viper_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_crashviper (1 .5 0) (-176 -120 -24) (176 120 72) 
+This is a large viper about to crash
+*/
+void SP_misc_crashviper (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_viper_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) 
+This is a large stationary viper as seen in Paul's intro
+*/
+void SP_misc_bigviper (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -176, -120, -24);
+	VectorSet (ent->maxs, 176, 120, 72);
+	ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8)
+"dmg"	how much boom should the bomb make?
+*/
+void misc_viper_bomb_touch (edict_t *self, edict_t *, cplane_t *, csurface_t *)
+{
+	G_UseTargets (self, self->activator);
+
+	self->s.origin[2] = self->absmin[2] + 1;
+	T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB);
+	BecomeExplosion2 (self);
+}
+
+void misc_viper_bomb_prethink (edict_t *self)
+{
+	vec3_t	v;
+	float	diff;
+
+	self->groundentity = NULL;
+
+	diff = self->timestamp - level.time;
+	if (diff < -1.0)
+		diff = -1.0;
+
+	VectorScale (self->moveinfo.dir, 1.0 + diff, v);
+	v[2] = diff;
+
+	diff = self->s.angles[2];
+	vectoangles (v, self->s.angles);
+	self->s.angles[2] = diff + 10;
+}
+
+void misc_viper_bomb_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	edict_t	*viper;
+
+	self->solid = SOLID_BBOX;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->s.effects |= EF_ROCKET;
+	self->use = NULL;
+	self->movetype = MOVETYPE_TOSS;
+	self->prethink = misc_viper_bomb_prethink;
+	self->touch = misc_viper_bomb_touch;
+	self->activator = activator;
+
+	viper = G_Find (NULL, FOFS(classname), "misc_viper");
+	VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity);
+
+	self->timestamp = level.time;
+	VectorCopy (viper->moveinfo.dir, self->moveinfo.dir);
+}
+
+
+void SP_misc_viper_bomb (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+
+	self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
+
+	if (!self->dmg)
+		self->dmg = 1000;
+
+	self->use = misc_viper_bomb_use;
+	self->svflags |= SVF_NOCLIENT;
+
+	gi.linkentity (self);
+}
+
+
+// RAFAEL
+/*QUAKED misc_viper_missile (1 0 0) (-8 -8 -8) (8 8 8)
+"dmg"	how much boom should the bomb make? the default value is 250
+*/
+
+void misc_viper_missile_use (edict_t *self, edict_t *, edict_t *)
+{
+	
+	vec3_t	forward, right, up;
+	vec3_t	start, dir;
+	vec3_t	vec;
+	
+	AngleVectors (self->s.angles, forward, right, up);
+	
+	self->enemy = G_Find (NULL, FOFS(targetname), self->target);
+	
+	VectorCopy (self->enemy->s.origin, vec);
+	//vec[2] + 16;
+	
+	VectorCopy (self->s.origin, start);
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	
+	monster_fire_rocket (self, start, dir, self->dmg, 500, MZ2_CHICK_ROCKET_1);
+	
+	self->nextthink = level.time + 0.1;
+	self->think = G_FreeEdict;
+
+	
+}
+
+
+void SP_misc_viper_missile (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+
+	if (!self->dmg)
+		self->dmg = 250;
+
+	self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2");
+
+	self->use = misc_viper_missile_use;
+	self->svflags |= SVF_NOCLIENT;
+
+	gi.linkentity (self);
+}
+
+
+/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32)
+This is a Storgg ship for the flybys.
+It is trigger_spawned, so you must have something use it for it to show up.
+There must be a path for it to follow once it is activated.
+
+"speed"		How fast it should fly
+*/
+
+extern void train_use (edict_t *self, edict_t *other, edict_t *activator);
+extern void func_train_find (edict_t *self);
+
+void misc_strogg_ship_use  (edict_t *self, edict_t *other, edict_t *activator)
+{
+	self->svflags &= ~SVF_NOCLIENT;
+	self->use = train_use;
+	train_use (self, other, activator);
+}
+
+void SP_misc_strogg_ship (edict_t *ent)
+{
+	if (!ent->target)
+	{
+		gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2");
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_strogg_ship_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	gi.linkentity (ent);
+}
+
+
+// RAFAEL 17-APR-98
+/*QUAKED misc_transport (1 0 0) (-8 -8 -8) (8 8 8) TRIGGER_SPAWN
+Maxx's transport at end of game
+*/
+void SP_misc_transport (edict_t *ent)
+{
+	
+	if (!ent->target)
+	{
+		gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->speed)
+		ent->speed = 300;
+
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_NOT;
+	ent->s.modelindex = gi.modelindex ("models/objects/ship/tris.md2");
+
+	VectorSet (ent->mins, -16, -16, 0);
+	VectorSet (ent->maxs, 16, 16, 32);
+
+	ent->think = func_train_find;
+	ent->nextthink = level.time + FRAMETIME;
+	ent->use = misc_strogg_ship_use;
+	ent->svflags |= SVF_NOCLIENT;
+	ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed;
+
+	if (!(ent->spawnflags & 1))
+	{
+		ent->spawnflags |= 1;
+	}
+	
+	gi.linkentity (ent);
+}
+// END 17-APR-98
+
+
+/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+*/
+void misc_satellite_dish_think (edict_t *self)
+{
+	self->s.frame++;
+	if (self->s.frame < 38)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void misc_satellite_dish_use (edict_t *self, edict_t *, edict_t *)
+{
+	self->s.frame = 0;
+	self->think = misc_satellite_dish_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void SP_misc_satellite_dish (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	VectorSet (ent->mins, -64, -64, 0);
+	VectorSet (ent->maxs, 64, 64, 128);
+	ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2");
+	ent->use = misc_satellite_dish_use;
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine1 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+*/
+void SP_light_mine2 (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_BBOX;
+	ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2");
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_arm (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/arm/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_leg (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/leg/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8)
+Intended for use with the target_spawner
+*/
+void SP_misc_gib_head (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/gibs/head/tris.md2");
+	ent->solid = SOLID_NOT;
+	ent->s.effects |= EF_GIB;
+	ent->takedamage = DAMAGE_YES;
+	ent->die = gib_die;
+	ent->movetype = MOVETYPE_TOSS;
+	ent->svflags |= SVF_MONSTER;
+	ent->deadflag = DEAD_DEAD;
+	ent->avelocity[0] = qrandom()*200;
+	ent->avelocity[1] = qrandom()*200;
+	ent->avelocity[2] = qrandom()*200;
+	ent->think = G_FreeEdict;
+	ent->nextthink = level.time + 30;
+	gi.linkentity (ent);
+}
+
+//=====================================================
+
+/*QUAKED target_character (0 0 1) ?
+used with target_string (must be on same "team")
+"count" is position in the string (starts at 1)
+*/
+
+void SP_target_character (edict_t *self)
+{
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+	self->solid = SOLID_BSP;
+	self->s.frame = 12;
+	gi.linkentity (self);
+	return;
+}
+
+
+/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+*/
+
+void target_string_use (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t *e;
+	int		n, l;
+	char	c;
+
+	l = strlen(self->message);
+	for (e = self->teammaster; e; e = e->teamchain)
+	{
+		if (!e->count)
+			continue;
+		n = e->count - 1;
+		if (n > l)
+		{
+			e->s.frame = 12;
+			continue;
+		}
+
+		c = self->message[n];
+		if (c >= '0' && c <= '9')
+			e->s.frame = c - '0';
+		else if (c == '-')
+			e->s.frame = 10;
+		else if (c == ':')
+			e->s.frame = 11;
+		else
+			e->s.frame = 12;
+	}
+}
+
+void SP_target_string (edict_t *self)
+{
+	if (!self->message)
+		self->message = "";
+	self->use = target_string_use;
+}
+
+
+/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE
+target a target_string with this
+
+The default is to be a time of day clock
+
+TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+If START_OFF, this entity must be used before it starts
+
+"style"		0 "xx"
+			1 "xx:xx"
+			2 "xx:xx:xx"
+*/
+
+#define	CLOCK_MESSAGE_SIZE	16
+
+// don't let field width of any clock messages change, or it
+// could cause an overwrite after a game load
+
+static void func_clock_reset (edict_t *self)
+{
+	self->activator = NULL;
+	if (self->spawnflags & 1)
+	{
+		self->health = 0;
+		self->wait = self->count;
+	}
+	else if (self->spawnflags & 2)
+	{
+		self->health = self->count;
+		self->wait = 0;
+	}
+}
+
+static void func_clock_format_countdown (edict_t *self)
+{
+	if (self->style == 0)
+	{
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health);
+		return;
+	}
+
+	if (self->style == 1)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		return;
+	}
+
+	if (self->style == 2)
+	{
+		Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+		return;
+	}
+}
+
+void func_clock_think (edict_t *self)
+{
+	Tm *t;
+
+	if (!self->enemy)
+	{
+		self->enemy = G_Find (NULL, FOFS(targetname), self->target);
+		if (!self->enemy)
+			return;
+	}
+
+	if (self->spawnflags & 1)
+	{
+		func_clock_format_countdown (self);
+		self->health++;
+	}
+	else if (self->spawnflags & 2)
+	{
+		func_clock_format_countdown (self);
+		self->health--;
+	}
+	else
+	{
+		t = localtime(time(nil));
+		Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2d:%2d:%2d", t->hour, t->min, t->sec);
+		if (self->message[3] == ' ')
+			self->message[3] = '0';
+		if (self->message[6] == ' ')
+			self->message[6] = '0';
+	}
+
+	self->enemy->message = self->message;
+	self->enemy->use (self->enemy, self, self);
+
+	if (((self->spawnflags & 1) && (self->health > self->wait)) ||
+		((self->spawnflags & 2) && (self->health < self->wait)))
+	{
+		if (self->pathtarget)
+		{
+			char *savetarget;
+			char *savemessage;
+
+			savetarget = self->target;
+			savemessage = self->message;
+			self->target = self->pathtarget;
+			self->message = NULL;
+			G_UseTargets (self, self->activator);
+			self->target = savetarget;
+			self->message = savemessage;
+		}
+
+		if (!(self->spawnflags & 8))
+			return;
+
+		func_clock_reset (self);
+
+		if (self->spawnflags & 4)
+			return;
+	}
+
+	self->nextthink = level.time + 1;
+}
+
+void func_clock_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (!(self->spawnflags & 8))
+		self->use = NULL;
+	if (self->activator)
+		return;
+	self->activator = activator;
+	self->think (self);
+}
+
+void SP_func_clock (edict_t *self)
+{
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 2) && (!self->count))
+	{
+		gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if ((self->spawnflags & 1) && (!self->count))
+		self->count = 60*60;;
+
+	func_clock_reset (self);
+
+	self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL);
+
+	self->think = func_clock_think;
+
+	if (self->spawnflags & 4)
+		self->use = func_clock_use;
+	else
+		self->nextthink = level.time + 1;
+}
+
+//=================================================================================
+
+void teleporter_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	edict_t		*dest;
+	int			i;
+
+	if (!other->client)
+		return;
+	dest = G_Find (NULL, FOFS(targetname), self->target);
+	if (!dest)
+	{
+		gi.dprintf ("Couldn't find destination\n");
+		return;
+	}
+
+	// unlink to make sure it can't possibly interfere with KillBox
+	gi.unlinkentity (other);
+
+	VectorCopy (dest->s.origin, other->s.origin);
+	VectorCopy (dest->s.origin, other->s.old_origin);
+	other->s.origin[2] += 10;
+
+	// clear the velocity and hold them in place briefly
+	VectorClear (other->velocity);
+	other->client->ps.pmove.pm_time = 160>>3;		// hold time
+	other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
+
+	// draw the teleport splash at source and on the player
+	self->owner->s.event = EV_PLAYER_TELEPORT;
+	other->s.event = EV_PLAYER_TELEPORT;
+
+	// set angles
+	for (i=0 ; i<3 ; i++)
+		other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
+
+	VectorClear (other->s.angles);
+	VectorClear (other->client->ps.viewangles);
+	VectorClear (other->client->v_angle);
+
+	// kill anything at the destination
+	KillBox (other);
+
+	gi.linkentity (other);
+}
+
+/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16)
+Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object.
+*/
+void SP_misc_teleporter (edict_t *ent)
+{
+	edict_t		*trig;
+
+	if (!ent->target)
+	{
+		gi.dprintf ("teleporter without a target.\n");
+		G_FreeEdict (ent);
+		return;
+	}
+
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 1;
+	ent->s.effects = EF_TELEPORTER;
+	ent->s.sound = gi.soundindex ("world/amb10.wav");
+	ent->solid = SOLID_BBOX;
+
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+
+	trig = G_Spawn ();
+	trig->touch = teleporter_touch;
+	trig->solid = SOLID_TRIGGER;
+	trig->target = ent->target;
+	trig->owner = ent;
+	VectorCopy (ent->s.origin, trig->s.origin);
+	VectorSet (trig->mins, -8, -8, 8);
+	VectorSet (trig->maxs, 8, 8, 24);
+	gi.linkentity (trig);
+	
+}
+
+/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16)
+Point teleporters at these.
+*/
+void SP_misc_teleporter_dest (edict_t *ent)
+{
+	gi.setmodel (ent, "models/objects/dmspot/tris.md2");
+	ent->s.skinnum = 0;
+	ent->solid = SOLID_BBOX;
+//	ent->s.effects |= EF_FLIES;
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, -16);
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16)
+Mal's amb4 loop entity
+*/
+static int amb4sound;
+
+void amb4_think (edict_t *ent)
+{
+	ent->nextthink = level.time + 2.7;
+	gi.sound(ent, CHAN_VOICE, amb4sound, 1, ATTN_NONE, 0);
+}
+
+void SP_misc_amb4 (edict_t *ent)
+{
+	ent->think = amb4_think;
+	ent->nextthink = level.time + 1;
+	amb4sound = gi.soundindex ("world/amb4.wav");
+	gi.linkentity (ent);
+}
+
+/*QUAKED misc_nuke (1 0 0) (-16 -16 -16) (16 16 16)
+*/
+
+void use_nuke (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t *from = g_edicts;
+
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (from == self)
+			continue;
+		if (from->client)
+		{
+			T_Damage (from, self, self, vec3_origin, from->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP);
+		}
+		else if (from->svflags & SVF_MONSTER)
+		{
+			G_FreeEdict (from);
+		}
+	}
+
+	self->use = NULL;
+}
+
+void SP_misc_nuke (edict_t *ent)
+{
+	ent->use = use_nuke;
+}
--- /dev/null
+++ b/xatrix/g_monster.c
@@ -1,0 +1,879 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+//
+// monster weapons
+//
+
+//FIXME mosnters should call these with a totally accurate direction
+// and we can mess it up based on skill.  Spread should be for normal
+// and we can tighten or loosen based on skill.  We could muck with
+// the damages too, but I'm not sure that's such a good idea.
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype)
+{
+	fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype)
+{
+	fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
+{
+	fire_blaster (self, start, dir, damage, speed, effect, false);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+// RAFAEL
+void monster_fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int, int effect)
+{
+	fire_blueblaster (self, start, dir, damage, speed, effect);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (MZ_BLUEHYPERBLASTER);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+// RAFAEL
+void monster_fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect)
+{
+	fire_ionripper (self, start, dir, damage, speed, effect);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+// RAFAEL
+void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
+{
+	fire_heat (self, start, dir, damage, speed, damage, damage);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+// RAFAEL
+void dabeam_hit (edict_t *self)
+{
+	edict_t	*ignore;
+	vec3_t	start;
+	vec3_t	end;
+	trace_t	tr;
+	static	vec3_t	lmins = {-4, -4, -4};
+	static	vec3_t	lmaxs = {4, 4, 4};
+
+	/*
+	int count
+	if (self->spawnflags & 0x80000000)
+		count = 8;
+	else
+		count = 4;
+	*/
+
+	ignore = self;
+	VectorCopy (self->s.origin, start);
+	VectorMA (start, 2048, self->movedir, end);
+	
+	while(1)
+	{
+		tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+		if (!tr.ent)
+			break;
+
+		// hurt it if we can
+		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
+			T_Damage (tr.ent, self, self->owner, self->movedir, tr.endpos, vec3_origin, self->dmg, skill->value, DAMAGE_ENERGY, MOD_TARGET_LASER);
+
+		if (self->dmg < 0) // healer ray
+		{
+			// when player is at 100 health
+			// just undo health fix
+			// keeping fx
+			if (tr.ent->client && tr.ent->health > 100)
+				tr.ent->health += self->dmg; 
+		}
+		
+		// if we hit something that's not a monster or player or is immune to lasers, we're done
+		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		{
+			if (self->spawnflags & 0x80000000)
+			{
+				self->spawnflags &= ~0x80000000;
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (10);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+			}
+			break;
+		}
+
+		ignore = tr.ent;
+		VectorCopy (tr.endpos, start);
+	}
+
+
+	VectorCopy (tr.endpos, self->s.old_origin);
+	self->nextthink = level.time + 0.1;
+	self->think = G_FreeEdict;
+  
+}
+
+// RAFAEL
+void monster_dabeam (edict_t *self)
+{
+	vec3_t last_movedir;
+	vec3_t point;
+	
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
+	self->s.modelindex = 1;
+		
+	self->s.frame = 2;
+	
+	if (self->owner->monsterinfo.aiflags & AI_MEDIC)
+		self->s.skinnum = 0xf3f3f1f1;
+	else
+		self->s.skinnum = 0xf2f2f0f0;
+
+	if (self->enemy)
+	{
+		VectorCopy (self->movedir, last_movedir);
+		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
+		if (self->owner->monsterinfo.aiflags & AI_MEDIC)
+			point[0] += sin (level.time) * 8;
+		VectorSubtract (point, self->s.origin, self->movedir);
+		VectorNormalize (self->movedir);
+		if (!VectorCompare(self->movedir, last_movedir))
+			self->spawnflags |= 0x80000000;
+	}
+	else
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	self->think = dabeam_hit;
+	self->nextthink = level.time + 0.1;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	gi.linkentity (self);
+ 
+	self->spawnflags |= 0x80000001;
+	self->svflags &= ~SVF_NOCLIENT;
+}
+
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype)
+{
+	fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype)
+{
+	fire_rocket (self, start, dir, damage, speed, damage+20, damage);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}	
+
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype)
+{
+	fire_rail (self, start, aimdir, damage, kick);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int, float damage_radius, int flashtype)
+{
+	fire_bfg (self, start, aimdir, damage, speed, damage_radius);
+
+	gi.WriteByte (svc_muzzleflash2);
+	gi.WriteShort (self - g_edicts);
+	gi.WriteByte (flashtype);
+	gi.multicast (start, MULTICAST_PVS);
+}
+
+
+
+//
+// Monster utility functions
+//
+
+static void M_FliesOff (edict_t *self)
+{
+	self->s.effects &= ~EF_FLIES;
+	self->s.sound = 0;
+}
+
+static void M_FliesOn (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+	self->s.effects |= EF_FLIES;
+	self->s.sound = gi.soundindex ("infantry/inflies1.wav");
+	self->think = M_FliesOff;
+	self->nextthink = level.time + 60;
+}
+
+void M_FlyCheck (edict_t *self)
+{
+	if (self->waterlevel)
+		return;
+
+	if (qrandom() > 0.5)
+		return;
+
+	self->think = M_FliesOn;
+	self->nextthink = level.time + 5 + 10 * qrandom();
+}
+
+void AttackFinished (edict_t *self, float time)
+{
+	self->monsterinfo.attack_finished = level.time + time;
+}
+
+
+void M_CheckGround (edict_t *ent)
+{
+	vec3_t		point;
+	trace_t		trace;
+
+	if (ent->flags & (FL_SWIM|FL_FLY))
+		return;
+
+	if (ent->velocity[2] > 100)
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+
+// if the hull point one-quarter unit down is solid the entity is on ground
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+	point[2] = ent->s.origin[2] - 0.25;
+
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID);
+
+	// check steepness
+	if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
+	{
+		ent->groundentity = NULL;
+		return;
+	}
+
+//	ent->groundentity = trace.ent;
+//	ent->groundentity_linkcount = trace.ent->linkcount;
+//	if (!trace.startsolid && !trace.allsolid)
+//		VectorCopy (trace.endpos, ent->s.origin);
+	if (!trace.startsolid && !trace.allsolid)
+	{
+		VectorCopy (trace.endpos, ent->s.origin);
+		ent->groundentity = trace.ent;
+		ent->groundentity_linkcount = trace.ent->linkcount;
+		ent->velocity[2] = 0;
+	}
+}
+
+
+void M_CatagorizePosition (edict_t *ent)
+{
+	vec3_t		point;
+	int			cont;
+
+//
+// get waterlevel
+//
+	point[0] = ent->s.origin[0];
+	point[1] = ent->s.origin[1];
+	point[2] = ent->s.origin[2] + ent->mins[2] + 1;	
+	cont = gi.pointcontents (point);
+
+	if (!(cont & MASK_WATER))
+	{
+		ent->waterlevel = 0;
+		ent->watertype = 0;
+		return;
+	}
+
+	ent->watertype = cont;
+	ent->waterlevel = 1;
+	point[2] += 26;
+	cont = gi.pointcontents (point);
+	if (!(cont & MASK_WATER))
+		return;
+
+	ent->waterlevel = 2;
+	point[2] += 22;
+	cont = gi.pointcontents (point);
+	if (cont & MASK_WATER)
+		ent->waterlevel = 3;
+}
+
+
+void M_WorldEffects (edict_t *ent)
+{
+	int		dmg;
+
+	if (ent->health > 0)
+	{
+		if (!(ent->flags & FL_SWIM))
+		{
+			if (ent->waterlevel < 3)
+			{
+				ent->air_finished = level.time + 12;
+			}
+			else if (ent->air_finished < level.time)
+			{	// drown!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+		else
+		{
+			if (ent->waterlevel > 0)
+			{
+				ent->air_finished = level.time + 9;
+			}
+			else if (ent->air_finished < level.time)
+			{	// suffocate!
+				if (ent->pain_debounce_time < level.time)
+				{
+					dmg = 2 + 2 * floor(level.time - ent->air_finished);
+					if (dmg > 15)
+						dmg = 15;
+					T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+					ent->pain_debounce_time = level.time + 1;
+				}
+			}
+		}
+	}
+	
+	if (ent->waterlevel == 0)
+	{
+		if (ent->flags & FL_INWATER)
+		{	
+			gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+			ent->flags &= ~FL_INWATER;
+		}
+		return;
+	}
+
+	if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 0.2;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA);
+		}
+	}
+	if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME))
+	{
+		if (ent->damage_debounce_time < level.time)
+		{
+			ent->damage_debounce_time = level.time + 1;
+			T_Damage (ent, WORLD, WORLD, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME);
+		}
+	}
+	
+	if ( !(ent->flags & FL_INWATER) )
+	{	
+		if (!(ent->svflags & SVF_DEADMONSTER))
+		{
+			if (ent->watertype & CONTENTS_LAVA)
+				if (qrandom() <= 0.5)
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_SLIME)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+			else if (ent->watertype & CONTENTS_WATER)
+				gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		}
+
+		ent->flags |= FL_INWATER;
+		ent->damage_debounce_time = 0;
+	}
+}
+
+
+void M_droptofloor (edict_t *ent)
+{
+	vec3_t		end;
+	trace_t		trace;
+
+	ent->s.origin[2] += 1;
+	VectorCopy (ent->s.origin, end);
+	end[2] -= 256;
+	
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1 || trace.allsolid)
+		return;
+
+	VectorCopy (trace.endpos, ent->s.origin);
+
+	gi.linkentity (ent);
+	M_CheckGround (ent);
+	M_CatagorizePosition (ent);
+}
+
+
+void M_SetEffects (edict_t *ent)
+{
+	ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN);
+	ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
+
+	if (ent->monsterinfo.aiflags & AI_RESURRECTING)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= RF_SHELL_RED;
+	}
+
+	if (ent->health <= 0)
+		return;
+
+	if (ent->powerarmor_time > level.time)
+	{
+		if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+}
+
+
+void M_MoveFrame (edict_t *self)
+{
+	mmove_t	*move;
+	int		index;
+
+	move = self->monsterinfo.currentmove;
+	self->nextthink = level.time + FRAMETIME;
+
+	if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe))
+	{
+		self->s.frame = self->monsterinfo.nextframe;
+		self->monsterinfo.nextframe = 0;
+	}
+	else
+	{
+		if (self->s.frame == move->lastframe)
+		{
+			if (move->endfunc)
+			{
+				move->endfunc (self);
+
+				// regrab move, endfunc is very likely to change it
+				move = self->monsterinfo.currentmove;
+
+				// check for death
+				if (self->svflags & SVF_DEADMONSTER)
+					return;
+			}
+		}
+
+		if (self->s.frame < move->firstframe || self->s.frame > move->lastframe)
+		{
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+			self->s.frame = move->firstframe;
+		}
+		else
+		{
+			if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			{
+				self->s.frame++;
+				if (self->s.frame > move->lastframe)
+					self->s.frame = move->firstframe;
+			}
+		}
+	}
+
+	index = self->s.frame - move->firstframe;
+	if (move->frame[index].aifunc)
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale);
+		else
+			move->frame[index].aifunc (self, 0);
+
+	if (move->frame[index].thinkfunc)
+		move->frame[index].thinkfunc (self);
+}
+
+
+void monster_think (edict_t *self)
+{
+	M_MoveFrame (self);
+	if (self->linkcount != self->monsterinfo.linkcount)
+	{
+		self->monsterinfo.linkcount = self->linkcount;
+		M_CheckGround (self);
+	}
+	M_CatagorizePosition (self);
+	M_WorldEffects (self);
+	M_SetEffects (self);
+}
+
+
+/*
+================
+monster_use
+
+Using a monster makes it angry at the current activator
+================
+*/
+void monster_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->enemy)
+		return;
+	if (self->health <= 0)
+		return;
+	if (activator->flags & FL_NOTARGET)
+		return;
+	if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY))
+		return;
+	
+// delay reaction so if the monster is teleported, its sound is still heard
+	self->enemy = activator;
+	FoundTarget (self);
+}
+
+
+void monster_start_go (edict_t *self);
+
+
+void monster_triggered_spawn (edict_t *self)
+{
+	self->s.origin[2] += 1;
+	KillBox (self);
+
+	self->solid = SOLID_BBOX;
+	self->movetype = MOVETYPE_STEP;
+	self->svflags &= ~SVF_NOCLIENT;
+	self->air_finished = level.time + 12;
+	gi.linkentity (self);
+
+	monster_start_go (self);
+
+	// RAFAEL
+	if (strcmp (self->classname, "monster_fixbot") == 0)
+	{
+		if (self->spawnflags &16 || self->spawnflags &8 || self->spawnflags &4)
+		{
+			self->enemy = NULL;
+			return;
+		}
+	}
+	
+	if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET))
+	{
+		FoundTarget (self);
+	}
+	else
+	{
+		self->enemy = NULL;
+	}
+}
+
+void monster_triggered_spawn_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	// we have a one frame delay here so we don't telefrag the guy who activated us
+	self->think = monster_triggered_spawn;
+	self->nextthink = level.time + FRAMETIME;
+	if (activator->client)
+		self->enemy = activator;
+	self->use = monster_use;
+}
+
+void monster_triggered_start (edict_t *self)
+{
+	self->solid = SOLID_NOT;
+	self->movetype = MOVETYPE_NONE;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+	self->use = monster_triggered_spawn_use;
+}
+
+
+/*
+================
+monster_death_use
+
+When a monster dies, it fires all of its targets with the current
+enemy as activator.
+================
+*/
+void monster_death_use (edict_t *self)
+{
+	self->flags &= ~(FL_FLY|FL_SWIM);
+	self->monsterinfo.aiflags &= AI_GOOD_GUY;
+
+	if (self->item)
+	{
+		Drop_Item (self, self->item);
+		self->item = NULL;
+	}
+
+	if (self->deathtarget)
+		self->target = self->deathtarget;
+
+	if (!self->target)
+		return;
+
+	G_UseTargets (self, self->enemy);
+}
+
+
+//============================================================================
+
+qboolean monster_start (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return false;
+	}
+
+	if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY))
+	{
+		self->spawnflags &= ~4;
+		self->spawnflags |= 1;
+//		gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin));
+	}
+
+	if (!(self->monsterinfo.aiflags & AI_GOOD_GUY))
+		level.total_monsters++;
+
+	self->nextthink = level.time + FRAMETIME;
+	self->svflags |= SVF_MONSTER;
+	self->s.renderfx |= RF_FRAMELERP;
+	self->takedamage = DAMAGE_AIM;
+	self->air_finished = level.time + 12;
+	self->use = monster_use;
+	self->max_health = self->health;
+	self->clipmask = MASK_MONSTERSOLID;
+
+	self->s.skinnum = 0;
+	self->deadflag = DEAD_NO;
+	self->svflags &= ~SVF_DEADMONSTER;
+
+	if (!self->monsterinfo.checkattack)
+		self->monsterinfo.checkattack = M_CheckAttack;
+	VectorCopy (self->s.origin, self->s.old_origin);
+
+	if (st.item)
+	{
+		self->item = FindItemByClassname (st.item);
+		if (!self->item)
+			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+	}
+
+	// randomize what frame they start on
+	if (self->monsterinfo.currentmove)
+		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
+
+	return true;
+}
+
+void monster_start_go (edict_t *self)
+{
+	vec3_t	v;
+
+	if (self->health <= 0)
+		return;
+
+	// check for target to combat_point and change to combattarget
+	if (self->target)
+	{
+		qboolean	notcombat;
+		qboolean	fixup;
+		edict_t		*target;
+
+		target = NULL;
+		notcombat = false;
+		fixup = false;
+		while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") == 0)
+			{
+				self->combattarget = self->target;
+				fixup = true;
+			}
+			else
+			{
+				notcombat = true;
+			}
+		}
+		if (notcombat && self->combattarget)
+			gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin));
+		if (fixup)
+			self->target = NULL;
+	}
+
+	// validate combattarget
+	if (self->combattarget)
+	{
+		edict_t		*target;
+
+		target = NULL;
+		while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL)
+		{
+			if (strcmp(target->classname, "point_combat") != 0)
+			{
+				gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n",
+					self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2],
+					self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1],
+					(int)target->s.origin[2]);
+			}
+		}
+	}
+
+	if (self->target)
+	{
+		self->goalentity = self->movetarget = G_PickTarget(self->target);
+		if (!self->movetarget)
+		{
+			gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
+			self->target = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+		else if (strcmp (self->movetarget->classname, "path_corner") == 0)
+		{
+			VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+			self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+			self->monsterinfo.walk (self);
+			self->target = NULL;
+		}
+		else
+		{
+			self->goalentity = self->movetarget = NULL;
+			self->monsterinfo.pausetime = 100000000;
+			self->monsterinfo.stand (self);
+		}
+	}
+	else
+	{
+		self->monsterinfo.pausetime = 100000000;
+		self->monsterinfo.stand (self);
+	}
+
+	self->think = monster_think;
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void walkmonster_start_go (edict_t *self)
+{
+	if (!(self->spawnflags & 2) && level.time < 1)
+	{
+		M_droptofloor (self);
+
+		if (self->groundentity)
+			if (!M_walkmove (self, 0, 0))
+				gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+	}
+	
+	if (!self->yaw_speed)
+		self->yaw_speed = 20;
+	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void walkmonster_start (edict_t *self)
+{
+	self->think = walkmonster_start_go;
+	monster_start (self);
+}
+
+
+void flymonster_start_go (edict_t *self)
+{
+	if (!M_walkmove (self, 0, 0))
+		gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 25;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+
+void flymonster_start (edict_t *self)
+{
+	self->flags |= FL_FLY;
+	self->think = flymonster_start_go;
+	monster_start (self);
+}
+
+
+void swimmonster_start_go (edict_t *self)
+{
+	if (!self->yaw_speed)
+		self->yaw_speed = 10;
+	self->viewheight = 10;
+
+	monster_start_go (self);
+
+	if (self->spawnflags & 2)
+		monster_triggered_start (self);
+}
+
+void swimmonster_start (edict_t *self)
+{
+	self->flags |= FL_SWIM;
+	self->think = swimmonster_start_go;
+	monster_start (self);
+}
--- /dev/null
+++ b/xatrix/g_phys.c
@@ -1,0 +1,958 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*
+
+
+pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move.
+
+onground is set for toss objects when they come to a complete rest.  it is set for steping or walking objects 
+
+doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH
+bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS
+corpses are SOLID_NOT and MOVETYPE_TOSS
+crates are SOLID_BBOX and MOVETYPE_TOSS
+walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP
+flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY
+
+solid_edge items only clip against bsp models.
+
+*/
+
+
+/*
+============
+SV_TestEntityPosition
+
+============
+*/
+edict_t	*SV_TestEntityPosition (edict_t *ent)
+{
+	trace_t	trace;
+	int		mask;
+
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+	trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask);
+	
+	if (trace.startsolid)
+		return g_edicts;
+		
+	return NULL;
+}
+
+
+/*
+================
+SV_CheckVelocity
+================
+*/
+void SV_CheckVelocity (edict_t *ent)
+{
+	int		i;
+
+//
+// bound velocity
+//
+	for (i=0 ; i<3 ; i++)
+	{
+		if (ent->velocity[i] > sv_maxvelocity->value)
+			ent->velocity[i] = sv_maxvelocity->value;
+		else if (ent->velocity[i] < -sv_maxvelocity->value)
+			ent->velocity[i] = -sv_maxvelocity->value;
+	}
+}
+
+/*
+=============
+SV_RunThink
+
+Runs thinking code for this frame if necessary
+=============
+*/
+qboolean SV_RunThink (edict_t *ent)
+{
+	float	thinktime;
+
+	thinktime = ent->nextthink;
+	if (thinktime <= 0)
+		return true;
+	if (thinktime > level.time+0.001)
+		return true;
+	
+	ent->nextthink = 0;
+	if (!ent->think)
+		gi.error ("NULL ent->think");
+	ent->think (ent);
+
+	return false;
+}
+
+/*
+==================
+SV_Impact
+
+Two entities have touched, so run their touch functions
+==================
+*/
+void SV_Impact (edict_t *e1, trace_t *trace)
+{
+	edict_t		*e2;
+//	cplane_t	backplane;
+
+	e2 = trace->ent;
+
+	if (e1->touch && e1->solid != SOLID_NOT)
+		e1->touch (e1, e2, &trace->plane, trace->surface);
+	
+	if (e2->touch && e2->solid != SOLID_NOT)
+		e2->touch (e2, e1, NULL, NULL);
+}
+
+
+/*
+==================
+ClipVelocity
+
+Slide off of the impacting object
+returns the blocked flags (1 = floor, 2 = step / wall)
+==================
+*/
+#define	STOP_EPSILON	0.1
+
+int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce)
+{
+	float	backoff;
+	float	change;
+	int		i, blocked;
+	
+	blocked = 0;
+	if (normal[2] > 0)
+		blocked |= 1;		// floor
+	if (!normal[2])
+		blocked |= 2;		// step
+	
+	backoff = DotProduct (in, normal) * overbounce;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		change = normal[i]*backoff;
+		out[i] = in[i] - change;
+		if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
+			out[i] = 0;
+	}
+
+	return blocked;
+}
+
+
+/*
+============
+SV_FlyMove
+
+The basic solid body movement clip that slides along multiple planes
+Returns the clipflags if the velocity was modified (hit something solid)
+1 = floor
+2 = wall / step
+4 = dead stop
+============
+*/
+#define	MAX_CLIP_PLANES	5
+int SV_FlyMove (edict_t *ent, float time, int mask)
+{
+	edict_t		*hit;
+	int			bumpcount, numbumps;
+	vec3_t		dir;
+	float		d;
+	int			numplanes;
+	vec3_t		planes[MAX_CLIP_PLANES];
+	vec3_t		primal_velocity, original_velocity, new_velocity;
+	int			i, j;
+	trace_t		trace;
+	vec3_t		end;
+	float		time_left;
+	int			blocked;
+	
+	numbumps = 4;
+	
+	blocked = 0;
+	VectorCopy (ent->velocity, original_velocity);
+	VectorCopy (ent->velocity, primal_velocity);
+	numplanes = 0;
+	
+	time_left = time;
+
+	ent->groundentity = NULL;
+	for (bumpcount=0 ; bumpcount<numbumps ; bumpcount++)
+	{
+		for (i=0 ; i<3 ; i++)
+			end[i] = ent->s.origin[i] + time_left * ent->velocity[i];
+
+		trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask);
+
+		if (trace.allsolid)
+		{	// entity is trapped in another solid
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		if (trace.fraction > 0)
+		{	// actually covered some distance
+			VectorCopy (trace.endpos, ent->s.origin);
+			VectorCopy (ent->velocity, original_velocity);
+			numplanes = 0;
+		}
+
+		if (trace.fraction == 1)
+			 break;		// moved the entire distance
+
+		hit = trace.ent;
+
+		if (trace.plane.normal[2] > 0.7)
+		{
+			blocked |= 1;		// floor
+			if ( hit->solid == SOLID_BSP)
+			{
+				ent->groundentity = hit;
+				ent->groundentity_linkcount = hit->linkcount;
+			}
+		}
+		if (!trace.plane.normal[2])
+		{
+			blocked |= 2;		// step
+		}
+
+//
+// run the impact function
+//
+		SV_Impact (ent, &trace);
+		if (!ent->inuse)
+			break;		// removed by the impact function
+
+		
+		time_left -= time_left * trace.fraction;
+		
+	// cliped to another plane
+		if (numplanes >= MAX_CLIP_PLANES)
+		{	// this shouldn't really happen
+			VectorCopy (vec3_origin, ent->velocity);
+			return 3;
+		}
+
+		VectorCopy (trace.plane.normal, planes[numplanes]);
+		numplanes++;
+
+//
+// modify original_velocity so it parallels all of the clip planes
+//
+		for (i=0 ; i<numplanes ; i++)
+		{
+			ClipVelocity (original_velocity, planes[i], new_velocity, 1);
+
+			for (j=0 ; j<numplanes ; j++)
+				if ((j != i) && !VectorCompare (planes[i], planes[j]))
+				{
+					if (DotProduct (new_velocity, planes[j]) < 0)
+						break;	// not ok
+				}
+			if (j == numplanes)
+				break;
+		}
+		
+		if (i != numplanes)
+		{	// go along this plane
+			VectorCopy (new_velocity, ent->velocity);
+		}
+		else
+		{	// go along the crease
+			if (numplanes != 2)
+			{
+//				gi.dprintf ("clip velocity, numplanes == %i\n",numplanes);
+				VectorCopy (vec3_origin, ent->velocity);
+				return 7;
+			}
+			CrossProduct (planes[0], planes[1], dir);
+			d = DotProduct (dir, ent->velocity);
+			VectorScale (dir, d, ent->velocity);
+		}
+
+//
+// if original velocity is against the original velocity, stop dead
+// to avoid tiny occilations in sloping corners
+//
+		if (DotProduct (ent->velocity, primal_velocity) <= 0)
+		{
+			VectorCopy (vec3_origin, ent->velocity);
+			return blocked;
+		}
+	}
+
+	return blocked;
+}
+
+
+/*
+============
+SV_AddGravity
+
+============
+*/
+void SV_AddGravity (edict_t *ent)
+{
+	ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME;
+}
+
+/*
+===============================================================================
+
+PUSHMOVE
+
+===============================================================================
+*/
+
+/*
+============
+SV_PushEntity
+
+Does not change the entities velocity at all
+============
+*/
+trace_t SV_PushEntity (edict_t *ent, vec3_t push)
+{
+	trace_t	trace;
+	vec3_t	start;
+	vec3_t	end;
+	int		mask;
+
+	VectorCopy (ent->s.origin, start);
+	VectorAdd (start, push, end);
+
+retry:
+	if (ent->clipmask)
+		mask = ent->clipmask;
+	else
+		mask = MASK_SOLID;
+
+	trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask);
+	
+	VectorCopy (trace.endpos, ent->s.origin);
+	gi.linkentity (ent);
+
+	if (trace.fraction != 1.0)
+	{
+		SV_Impact (ent, &trace);
+
+		// if the pushed entity went away and the pusher is still there
+		if (!trace.ent->inuse && ent->inuse)
+		{
+			// move the pusher back and try again
+			VectorCopy (start, ent->s.origin);
+			gi.linkentity (ent);
+			goto retry;
+		}
+	}
+
+	if (ent->inuse)
+		G_TouchTriggers (ent);
+
+	return trace;
+}					
+
+
+typedef struct
+{
+	edict_t	*ent;
+	vec3_t	origin;
+	vec3_t	angles;
+	float	deltayaw;
+} pushed_t;
+pushed_t	pushed[MAX_EDICTS], *pushed_p;
+
+edict_t	*obstacle;
+
+/*
+============
+SV_Push
+
+Objects need to be moved back on a failed push,
+otherwise riders would continue to slide.
+============
+*/
+qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove)
+{
+	int			i, e;
+	edict_t		*check, *block;
+	vec3_t		mins, maxs;
+	pushed_t	*p;
+	vec3_t		org, org2, move2, forward, right, up;
+
+	// clamp the move to 1/8 units, so the position will
+	// be accurate for client side prediction
+	for (i=0 ; i<3 ; i++)
+	{
+		float	temp;
+		temp = move[i]*8.0;
+		if (temp > 0.0)
+			temp += 0.5;
+		else
+			temp -= 0.5;
+		move[i] = 0.125 * (int)temp;
+	}
+
+	// find the bounding box
+	for (i=0 ; i<3 ; i++)
+	{
+		mins[i] = pusher->absmin[i] + move[i];
+		maxs[i] = pusher->absmax[i] + move[i];
+	}
+
+// we need this for pushing things later
+	VectorSubtract (vec3_origin, amove, org);
+	AngleVectors (org, forward, right, up);
+
+// save the pusher's original position
+	pushed_p->ent = pusher;
+	VectorCopy (pusher->s.origin, pushed_p->origin);
+	VectorCopy (pusher->s.angles, pushed_p->angles);
+	if (pusher->client)
+		pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW];
+	pushed_p++;
+
+// move the pusher to it's final position
+	VectorAdd (pusher->s.origin, move, pusher->s.origin);
+	VectorAdd (pusher->s.angles, amove, pusher->s.angles);
+	gi.linkentity (pusher);
+
+// see if any solid entities are inside the final position
+	check = g_edicts+1;
+	for (e = 1; e < globals.num_edicts; e++, check++)
+	{
+		if (!check->inuse)
+			continue;
+		if (check->movetype == MOVETYPE_PUSH
+		|| check->movetype == MOVETYPE_STOP
+		|| check->movetype == MOVETYPE_NONE
+		|| check->movetype == MOVETYPE_NOCLIP)
+			continue;
+
+		if (!check->area.prev)
+			continue;		// not linked in anywhere
+
+	// if the entity is standing on the pusher, it will definitely be moved
+		if (check->groundentity != pusher)
+		{
+			// see if the ent needs to be tested
+			if ( check->absmin[0] >= maxs[0]
+			|| check->absmin[1] >= maxs[1]
+			|| check->absmin[2] >= maxs[2]
+			|| check->absmax[0] <= mins[0]
+			|| check->absmax[1] <= mins[1]
+			|| check->absmax[2] <= mins[2] )
+				continue;
+
+			// see if the ent's bbox is inside the pusher's final position
+			if (!SV_TestEntityPosition (check))
+				continue;
+		}
+
+		if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher))
+		{
+			// move this entity
+			pushed_p->ent = check;
+			VectorCopy (check->s.origin, pushed_p->origin);
+			VectorCopy (check->s.angles, pushed_p->angles);
+			pushed_p++;
+
+			// try moving the contacted entity 
+			VectorAdd (check->s.origin, move, check->s.origin);
+			if (check->client)
+			{	// FIXME: doesn't rotate monsters?
+				check->client->ps.pmove.delta_angles[YAW] += amove[YAW];
+			}
+
+			// figure movement due to the pusher's amove
+			VectorSubtract (check->s.origin, pusher->s.origin, org);
+			org2[0] = DotProduct (org, forward);
+			org2[1] = -DotProduct (org, right);
+			org2[2] = DotProduct (org, up);
+			VectorSubtract (org2, org, move2);
+			VectorAdd (check->s.origin, move2, check->s.origin);
+
+			// may have pushed them off an edge
+			if (check->groundentity != pusher)
+				check->groundentity = NULL;
+
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{	// pushed ok
+				gi.linkentity (check);
+				// impact?
+				continue;
+			}
+
+			// if it is ok to leave in the old position, do it
+			// this is only relevent for riding entities, not pushed
+			// FIXME: this doesn't acount for rotation
+			VectorSubtract (check->s.origin, move, check->s.origin);
+			block = SV_TestEntityPosition (check);
+			if (!block)
+			{
+				pushed_p--;
+				continue;
+			}
+		}
+		
+		// save off the obstacle so we can call the block function
+		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.origin);
+			VectorCopy (p->angles, p->ent->s.angles);
+			if (p->ent->client)
+			{
+				p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw;
+			}
+			gi.linkentity (p->ent);
+		}
+		return false;
+	}
+
+//FIXME: is there a better way to handle this?
+	// see if anything we moved has touched a trigger
+	for (p=pushed_p-1 ; p>=pushed ; p--)
+		G_TouchTriggers (p->ent);
+
+	return true;
+}
+
+/*
+================
+SV_Physics_Pusher
+
+Bmodel objects don't interact with each other, but
+push all box objects
+================
+*/
+void SV_Physics_Pusher (edict_t *ent)
+{
+	vec3_t		move, amove;
+	edict_t		*part, *mv;
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	// 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
+//retry:
+	pushed_p = pushed;
+	for (part = ent ; part ; part=part->teamchain)
+	{
+		if (part->velocity[0] || part->velocity[1] || part->velocity[2] ||
+			part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
+			)
+		{	// object is moving
+			VectorScale (part->velocity, FRAMETIME, move);
+			VectorScale (part->avelocity, FRAMETIME, amove);
+
+			if (!SV_Push (part, move, amove))
+				break;	// move was blocked
+		}
+	}
+	if (pushed_p > &pushed[MAX_EDICTS])
+		gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
+
+	if (part)
+	{
+		// the move failed, bump all nextthink times and back out moves
+		for (mv = ent ; mv ; mv=mv->teamchain)
+		{
+			if (mv->nextthink > 0)
+				mv->nextthink += FRAMETIME;
+		}
+
+		// if the pusher has a "blocked" function, call it
+		// otherwise, just stay in place until the obstacle is gone
+		if (part->blocked)
+			part->blocked (part, obstacle);
+/*
+		// if the pushed entity went away and the pusher is still there
+		if (!obstacle->inuse && part->inuse)
+			goto retry;
+*/
+	}
+	else
+	{
+		// the move succeeded, so call all think functions
+		for (part = ent ; part ; part=part->teamchain)
+		{
+			SV_RunThink (part);
+		}
+	}
+}
+
+//==================================================================
+
+/*
+=============
+SV_Physics_None
+
+Non moving objects can only think
+=============
+*/
+void SV_Physics_None (edict_t *ent)
+{
+// regular thinking
+	SV_RunThink (ent);
+}
+
+/*
+=============
+SV_Physics_Noclip
+
+A moving object that doesn't obey physics
+=============
+*/
+void SV_Physics_Noclip (edict_t *ent)
+{
+// regular thinking
+	if (!SV_RunThink (ent))
+		return;
+	
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin);
+
+	gi.linkentity (ent);
+}
+
+/*
+==============================================================================
+
+TOSS / BOUNCE
+
+==============================================================================
+*/
+
+/*
+=============
+SV_Physics_Toss
+
+Toss, bounce, and fly movement.  When onground, do nothing.
+=============
+*/
+void SV_Physics_Toss (edict_t *ent)
+{
+	trace_t		trace;
+	vec3_t		move;
+	float		backoff;
+	edict_t		*slave;
+	qboolean	wasinwater;
+	qboolean	isinwater;
+	vec3_t		old_origin;
+
+// regular thinking
+	SV_RunThink (ent);
+
+	// if not a team captain, so movement will be handled elsewhere
+	if ( ent->flags & FL_TEAMSLAVE)
+		return;
+
+	if (ent->velocity[2] > 0)
+		ent->groundentity = NULL;
+
+// check for the groundentity going away
+	if (ent->groundentity)
+		if (!ent->groundentity->inuse)
+			ent->groundentity = NULL;
+
+// if onground, return without moving
+	if ( ent->groundentity )
+		return;
+
+	VectorCopy (ent->s.origin, old_origin);
+
+	SV_CheckVelocity (ent);
+
+// add gravity
+	if (ent->movetype != MOVETYPE_FLY
+	&& ent->movetype != MOVETYPE_FLYMISSILE
+	// RAFAEL
+	// move type for rippergun projectile
+	&& ent->movetype != MOVETYPE_WALLBOUNCE)
+		SV_AddGravity (ent);
+
+// move angles
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+
+// move origin
+	VectorScale (ent->velocity, FRAMETIME, move);
+	trace = SV_PushEntity (ent, move);
+	if (!ent->inuse)
+		return;
+
+	if (trace.fraction < 1)
+	{
+		// RAFAEL
+		if (ent->movetype == MOVETYPE_WALLBOUNCE)
+			backoff = 2.0;
+		// RAFAEL ( else )		
+		else if (ent->movetype == MOVETYPE_BOUNCE)
+			backoff = 1.5;
+		else
+			backoff = 1;
+
+		ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff);
+
+		// RAFAEL
+		if (ent->movetype == MOVETYPE_WALLBOUNCE)
+			vectoangles (ent->velocity, ent->s.angles);
+
+	// stop if on ground
+		// RAFAEL
+		if (trace.plane.normal[2] > 0.7 && ent->movetype != MOVETYPE_WALLBOUNCE)
+		{		
+			if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE )
+			{
+				ent->groundentity = trace.ent;
+				ent->groundentity_linkcount = trace.ent->linkcount;
+				VectorCopy (vec3_origin, ent->velocity);
+				VectorCopy (vec3_origin, ent->avelocity);
+			}
+		}
+
+//		if (ent->touch)
+//			ent->touch (ent, trace.ent, &trace.plane, trace.surface);
+	}
+	
+// check for water transition
+	wasinwater = (ent->watertype & MASK_WATER);
+	ent->watertype = gi.pointcontents (ent->s.origin);
+	isinwater = ent->watertype & MASK_WATER;
+
+	if (isinwater)
+		ent->waterlevel = 1;
+	else
+		ent->waterlevel = 0;
+
+	if (!wasinwater && isinwater)
+		gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+	else if (wasinwater && !isinwater)
+		gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+
+// move teamslaves
+	for (slave = ent->teamchain; slave; slave = slave->teamchain)
+	{
+		VectorCopy (ent->s.origin, slave->s.origin);
+		gi.linkentity (slave);
+	}
+}
+
+/*
+===============================================================================
+
+STEPPING MOVEMENT
+
+===============================================================================
+*/
+
+/*
+=============
+SV_Physics_Step
+
+Monsters freefall when they don't have a ground entity, otherwise
+all movement is done with discrete steps.
+
+This is also used for objects that have become still on the ground, but
+will fall if the floor is pulled out from under them.
+FIXME: is this true?
+=============
+*/
+
+//FIXME: hacked in for E3 demo
+#define	sv_stopspeed		100
+#define sv_friction			6
+#define sv_waterfriction	1
+
+void SV_AddRotationalFriction (edict_t *ent)
+{
+	int		n;
+	float	adjustment;
+
+	VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles);
+	adjustment = FRAMETIME * sv_stopspeed * sv_friction;
+	for (n = 0; n < 3; n++)
+	{
+		if (ent->avelocity[n] > 0)
+		{
+			ent->avelocity[n] -= adjustment;
+			if (ent->avelocity[n] < 0)
+				ent->avelocity[n] = 0;
+		}
+		else
+		{
+			ent->avelocity[n] += adjustment;
+			if (ent->avelocity[n] > 0)
+				ent->avelocity[n] = 0;
+		}
+	}
+}
+
+void SV_Physics_Step (edict_t *ent)
+{
+	qboolean	wasonground;
+	qboolean	hitsound = false;
+	float		*vel;
+	float		speed, newspeed, control;
+	float		friction;
+	edict_t		*groundentity;
+	int			mask;
+
+	// airborn monsters should always check for ground
+	if (!ent->groundentity)
+		M_CheckGround (ent);
+
+	groundentity = ent->groundentity;
+
+	SV_CheckVelocity (ent);
+
+	if (groundentity)
+		wasonground = true;
+	else
+		wasonground = false;
+		
+	if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
+		SV_AddRotationalFriction (ent);
+
+	// add gravity except:
+	//   flying monsters
+	//   swimming monsters who are in the water
+	if (! wasonground)
+		if (!(ent->flags & FL_FLY))
+			if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2)))
+			{
+				if (ent->velocity[2] < sv_gravity->value*-0.1)
+					hitsound = true;
+				if (ent->waterlevel == 0)
+					SV_AddGravity (ent);
+			}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed ? sv_stopspeed : speed;
+		friction = sv_friction/3;
+		newspeed = speed - (FRAMETIME * control * friction);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	// friction for flying monsters that have been given vertical velocity
+	if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0))
+	{
+		speed = fabs(ent->velocity[2]);
+		control = speed < sv_stopspeed ? sv_stopspeed : speed;
+		newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel);
+		if (newspeed < 0)
+			newspeed = 0;
+		newspeed /= speed;
+		ent->velocity[2] *= newspeed;
+	}
+
+	if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0])
+	{
+		// apply friction
+		// let dead monsters who aren't completely onground slide
+		if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY)))
+			if (!(ent->health <= 0.0 && !M_CheckBottom(ent)))
+			{
+				vel = ent->velocity;
+				speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]);
+				if (speed)
+				{
+					friction = sv_friction;
+
+					control = speed < sv_stopspeed ? sv_stopspeed : speed;
+					newspeed = speed - FRAMETIME*control*friction;
+
+					if (newspeed < 0)
+						newspeed = 0;
+					newspeed /= speed;
+
+					vel[0] *= newspeed;
+					vel[1] *= newspeed;
+				}
+			}
+
+		if (ent->svflags & SVF_MONSTER)
+			mask = MASK_MONSTERSOLID;
+		else
+			mask = MASK_SOLID;
+		SV_FlyMove (ent, FRAMETIME, mask);
+
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+		if (!ent->inuse)
+			return;
+
+		if (ent->groundentity)
+			if (!wasonground)
+				if (hitsound)
+					gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0);
+	}
+
+// regular thinking
+	SV_RunThink (ent);
+}
+
+//============================================================================
+/*
+================
+G_RunEntity
+
+================
+*/
+void G_RunEntity (edict_t *ent)
+{
+	if (ent->prethink)
+		ent->prethink (ent);
+
+	switch ( (int)ent->movetype)
+	{
+	case MOVETYPE_PUSH:
+	case MOVETYPE_STOP:
+		SV_Physics_Pusher (ent);
+		break;
+	case MOVETYPE_NONE:
+		SV_Physics_None (ent);
+		break;
+	case MOVETYPE_NOCLIP:
+		SV_Physics_Noclip (ent);
+		break;
+	case MOVETYPE_STEP:
+		SV_Physics_Step (ent);
+		break;
+	case MOVETYPE_TOSS:
+	case MOVETYPE_BOUNCE:
+	case MOVETYPE_FLY:
+	case MOVETYPE_FLYMISSILE:
+	// RAFAEL
+	case MOVETYPE_WALLBOUNCE:
+		SV_Physics_Toss (ent);
+		break;
+	default:
+		gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype);			
+	}
+}
--- /dev/null
+++ b/xatrix/g_save.c
@@ -1,0 +1,753 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define Function(f) {#f, f}
+
+mmove_t mmove_reloc;
+
+field_t fields[] = {
+	{"classname", FOFS(classname), F_LSTRING},
+	{"model", FOFS(model), F_LSTRING},
+	{"spawnflags", FOFS(spawnflags), F_INT},
+	{"speed", FOFS(speed), F_FLOAT},
+	{"accel", FOFS(accel), F_FLOAT},
+	{"decel", FOFS(decel), F_FLOAT},
+	{"target", FOFS(target), F_LSTRING},
+	{"targetname", FOFS(targetname), F_LSTRING},
+	{"pathtarget", FOFS(pathtarget), F_LSTRING},
+	{"deathtarget", FOFS(deathtarget), F_LSTRING},
+	{"killtarget", FOFS(killtarget), F_LSTRING},
+	{"combattarget", FOFS(combattarget), F_LSTRING},
+	{"message", FOFS(message), F_LSTRING},
+	{"team", FOFS(team), F_LSTRING},
+	{"wait", FOFS(wait), F_FLOAT},
+	{"delay", FOFS(delay), F_FLOAT},
+	{"random", FOFS(random), F_FLOAT},
+	{"move_origin", FOFS(move_origin), F_VECTOR},
+	{"move_angles", FOFS(move_angles), F_VECTOR},
+	{"style", FOFS(style), F_INT},
+	{"count", FOFS(count), F_INT},
+	{"health", FOFS(health), F_INT},
+	{"sounds", FOFS(sounds), F_INT},
+	{"light", 0, F_IGNORE},
+	{"dmg", FOFS(dmg), F_INT},
+	{"mass", FOFS(mass), F_INT},
+	{"volume", FOFS(volume), F_FLOAT},
+	{"attenuation", FOFS(attenuation), F_FLOAT},
+	{"map", FOFS(map), F_LSTRING},
+	{"origin", FOFS(s.origin), F_VECTOR},
+	{"angles", FOFS(s.angles), F_VECTOR},
+	{"angle", FOFS(s.angles), F_ANGLEHACK},
+
+	{"goalentity", FOFS(goalentity), F_EDICT, FFL_NOSPAWN},
+	{"movetarget", FOFS(movetarget), F_EDICT, FFL_NOSPAWN},
+	{"enemy", FOFS(enemy), F_EDICT, FFL_NOSPAWN},
+	{"oldenemy", FOFS(oldenemy), F_EDICT, FFL_NOSPAWN},
+	{"activator", FOFS(activator), F_EDICT, FFL_NOSPAWN},
+	{"groundentity", FOFS(groundentity), F_EDICT, FFL_NOSPAWN},
+	{"teamchain", FOFS(teamchain), F_EDICT, FFL_NOSPAWN},
+	{"teammaster", FOFS(teammaster), F_EDICT, FFL_NOSPAWN},
+	{"owner", FOFS(owner), F_EDICT, FFL_NOSPAWN},
+	{"mynoise", FOFS(mynoise), F_EDICT, FFL_NOSPAWN},
+	{"mynoise2", FOFS(mynoise2), F_EDICT, FFL_NOSPAWN},
+	{"target_ent", FOFS(target_ent), F_EDICT, FFL_NOSPAWN},
+	{"chain", FOFS(chain), F_EDICT, FFL_NOSPAWN},
+
+	{"prethink", FOFS(prethink), F_FUNCTION, FFL_NOSPAWN},
+	{"think", FOFS(think), F_FUNCTION, FFL_NOSPAWN},
+	{"blocked", FOFS(blocked), F_FUNCTION, FFL_NOSPAWN},
+	{"touch", FOFS(touch), F_FUNCTION, FFL_NOSPAWN},
+	{"use", FOFS(use), F_FUNCTION, FFL_NOSPAWN},
+	{"pain", FOFS(pain), F_FUNCTION, FFL_NOSPAWN},
+	{"die", FOFS(die), F_FUNCTION, FFL_NOSPAWN},
+
+	{"stand", FOFS(monsterinfo.stand), F_FUNCTION, FFL_NOSPAWN},
+	{"idle", FOFS(monsterinfo.idle), F_FUNCTION, FFL_NOSPAWN},
+	{"search", FOFS(monsterinfo.search), F_FUNCTION, FFL_NOSPAWN},
+	{"walk", FOFS(monsterinfo.walk), F_FUNCTION, FFL_NOSPAWN},
+	{"run", FOFS(monsterinfo.run), F_FUNCTION, FFL_NOSPAWN},
+	{"dodge", FOFS(monsterinfo.dodge), F_FUNCTION, FFL_NOSPAWN},
+	{"attack", FOFS(monsterinfo.attack), F_FUNCTION, FFL_NOSPAWN},
+	{"melee", FOFS(monsterinfo.melee), F_FUNCTION, FFL_NOSPAWN},
+	{"sight", FOFS(monsterinfo.sight), F_FUNCTION, FFL_NOSPAWN},
+	{"checkattack", FOFS(monsterinfo.checkattack), F_FUNCTION, FFL_NOSPAWN},
+	{"currentmove", FOFS(monsterinfo.currentmove), F_MMOVE, FFL_NOSPAWN},
+
+	{"endfunc", FOFS(moveinfo.endfunc), F_FUNCTION, FFL_NOSPAWN},
+
+	// temp spawn vars -- only valid when the spawn function is called
+	{"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP},
+	{"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP},
+	{"height", STOFS(height), F_INT, FFL_SPAWNTEMP},
+	{"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP},
+	{"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP},
+	{"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP},
+
+//need for item field in edict struct, FFL_SPAWNTEMP item will be skipped on saves
+	{"item", FOFS(item), F_ITEM},
+
+	{"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP},
+	{"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP},
+	{"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP},
+	{"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP},
+	{"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP},
+	{"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP},
+	{"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP},
+
+	{0, 0, 0, 0}
+
+};
+
+field_t		levelfields[] =
+{
+	{"changemap", LLOFS(changemap), F_LSTRING},
+                   
+	{"sight_client", LLOFS(sight_client), F_EDICT},
+	{"sight_entity", LLOFS(sight_entity), F_EDICT},
+	{"sound_entity", LLOFS(sound_entity), F_EDICT},
+	{"sound2_entity", LLOFS(sound2_entity), F_EDICT},
+
+	{NULL, 0, F_INT}
+};
+
+field_t		clientfields[] =
+{
+	{"pers.weapon", CLOFS(pers.weapon), F_ITEM},
+	{"pers.lastweapon", CLOFS(pers.lastweapon), F_ITEM},
+	{"newweapon", CLOFS(newweapon), F_ITEM},
+
+	{NULL, 0, F_INT}
+};
+
+/*
+============
+InitGame
+
+This will be called when the dll is first loaded, which
+only happens when a new game is started or a save game
+is loaded.
+============
+*/
+void InitGame (void)
+{
+	gi.dprintf ("==== InitGame ====\n");
+
+	gun_x = gi.cvar ("gun_x", "0", 0);
+	gun_y = gi.cvar ("gun_y", "0", 0);
+	gun_z = gi.cvar ("gun_z", "0", 0);
+
+	//FIXME: sv_ prefix is wrong for these
+	sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0);
+	sv_rollangle = gi.cvar ("sv_rollangle", "2", 0);
+	sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0);
+	sv_gravity = gi.cvar ("sv_gravity", "800", 0);
+
+	// noset vars
+	dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET);
+
+	// latched vars
+	sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH);
+	gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH);
+	gi.cvar ("gamedate", "May 30 1998" , CVAR_SERVERINFO | CVAR_LATCH);
+
+	maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
+	maxspectators = gi.cvar ("maxspectators", "4", CVAR_SERVERINFO);
+	deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH);
+	coop = gi.cvar ("coop", "0", CVAR_LATCH);
+	skill = gi.cvar ("skill", "1", CVAR_LATCH);
+	maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH);
+
+	// change anytime vars
+	dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO);
+	fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO);
+	timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO);
+	password = gi.cvar ("password", "", CVAR_USERINFO);
+	spectator_password = gi.cvar ("spectator_password", "", 0);
+	filterban = gi.cvar ("filterban", "1", 0);
+
+	g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE);
+
+	run_pitch = gi.cvar ("run_pitch", "0.002", 0);
+	run_roll = gi.cvar ("run_roll", "0.005", 0);
+	bob_up  = gi.cvar ("bob_up", "0.005", 0);
+	bob_pitch = gi.cvar ("bob_pitch", "0.002", 0);
+	bob_roll = gi.cvar ("bob_roll", "0.002", 0);
+
+	// flood control
+	flood_msgs = gi.cvar ("flood_msgs", "4", 0);
+	flood_persecond = gi.cvar ("flood_persecond", "4", 0);
+	flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0);
+
+	// dm map list
+	sv_maplist = gi.cvar ("sv_maplist", "", 0);
+
+	// items
+	InitItems ();
+
+	Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), "");
+
+	Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), "");
+
+	// initialize all entities for this game
+	game.maxentities = maxentities->value;
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+	globals.max_edicts = game.maxentities;
+
+	// initialize all clients for this game
+	game.maxclients = maxclients->value;
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	globals.num_edicts = game.maxclients+1;
+}
+
+//=========================================================
+
+void WriteField1 (FILE *, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+	case F_GSTRING:
+		if ( *(char **)p )
+			len = strlen(*(char **)p) + 1;
+		else
+			len = 0;
+		*(int *)p = len;
+		break;
+	case F_EDICT:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(edict_t **)p - g_edicts;
+		*(int *)p = index;
+		break;
+	case F_CLIENT:
+		if ( *(gclient_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gclient_t **)p - game.clients;
+		*(int *)p = index;
+		break;
+	case F_ITEM:
+		if ( *(edict_t **)p == NULL)
+			index = -1;
+		else
+			index = *(gitem_t **)p - itemlist;
+		*(int *)p = index;
+		break;
+
+	//relative to code segment
+	case F_FUNCTION:
+		if (*(byte **)p == NULL)
+			index = 0;
+		else
+			index = *(byte **)p - ((byte *)InitGame);
+		*(int *)p = index;
+		break;
+
+	//relative to data segment
+	case F_MMOVE:
+		if (*(byte **)p == NULL)
+			index = 0;
+		else
+			index = *(byte **)p - (byte *)&mmove_reloc;
+		*(int *)p = index;
+		break;
+
+	default:
+		gi.error ("WriteEdict: unknown field type");
+	}
+}
+
+
+void WriteField2 (FILE *f, field_t *field, byte *base)
+{
+	int			len;
+	void		*p;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_LSTRING:
+		if ( *(char **)p )
+		{
+			len = strlen(*(char **)p) + 1;
+			fwrite (*(char **)p, len, 1, f);
+		}
+		break;
+	}
+}
+
+void ReadField (FILE *f, field_t *field, byte *base)
+{
+	void		*p;
+	int			len;
+	int			index;
+
+	if (field->flags & FFL_SPAWNTEMP)
+		return;
+
+	p = (void *)(base + field->ofs);
+	switch (field->type)
+	{
+	case F_INT:
+	case F_FLOAT:
+	case F_ANGLEHACK:
+	case F_VECTOR:
+	case F_IGNORE:
+		break;
+
+	case F_LSTRING:
+		len = *(int *)p;
+		if (!len)
+			*(char **)p = NULL;
+		else
+		{
+			*(char **)p = gi.TagMalloc (len, TAG_LEVEL);
+			fread (*(char **)p, len, 1, f);
+		}
+		break;
+	case F_EDICT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(edict_t **)p = NULL;
+		else
+			*(edict_t **)p = &g_edicts[index];
+		break;
+	case F_CLIENT:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gclient_t **)p = NULL;
+		else
+			*(gclient_t **)p = &game.clients[index];
+		break;
+	case F_ITEM:
+		index = *(int *)p;
+		if ( index == -1 )
+			*(gitem_t **)p = NULL;
+		else
+			*(gitem_t **)p = &itemlist[index];
+		break;
+
+	//relative to code segment
+	case F_FUNCTION:
+		index = *(int *)p;
+		if ( index == 0 )
+			*(byte **)p = NULL;
+		else
+			*(byte **)p = ((byte *)InitGame) + index;
+		break;
+
+	//relative to data segment
+	case F_MMOVE:
+		index = *(int *)p;
+		if (index == 0)
+			*(byte **)p = NULL;
+		else
+			*(byte **)p = (byte *)&mmove_reloc + index;
+		break;
+
+	default:
+		gi.error ("ReadEdict: unknown field type");
+	}
+}
+
+//=========================================================
+
+/*
+==============
+WriteClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+	gclient_t	temp;
+	
+	// all of the ints, floats, and vectors stay as they are
+	temp = *client;
+
+	// change the pointers to lengths or indexes
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=clientfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)client);
+	}
+}
+
+/*
+==============
+ReadClient
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadClient (FILE *f, gclient_t *client)
+{
+	field_t		*field;
+
+	fread (client, sizeof(*client), 1, f);
+
+	for (field=clientfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)client);
+	}
+}
+
+/*
+============
+WriteGame
+
+This will be called whenever the game goes to a new level,
+and when the user explicitly saves the game.
+
+Game information include cross level data, like multi level
+triggers, help computer info, and all client states.
+
+A single player death will automatically restore from the
+last save position.
+============
+*/
+void WriteGame (char *filename, qboolean autosave)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	if (!autosave)
+		SaveClientData ();
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	memset (str, 0, sizeof(str));
+	strcpy (str, "May 30 1998");
+	fwrite (str, sizeof(str), 1, f);
+
+	game.autosaved = autosave;
+	fwrite (&game, sizeof(game), 1, f);
+	game.autosaved = false;
+
+	for (i=0 ; i<game.maxclients ; i++)
+		WriteClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+void ReadGame (char *filename)
+{
+	FILE	*f;
+	int		i;
+	char	str[16];
+
+	gi.FreeTags (TAG_GAME);
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	fread (str, sizeof(str), 1, f);
+	if (strcmp (str, "May 30 1998"))
+	{
+		fclose (f);
+		gi.error ("Savegame from an older version.\n");
+	}
+
+	g_edicts =  gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME);
+	globals.edicts = g_edicts;
+
+	fread (&game, sizeof(game), 1, f);
+	game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME);
+	for (i=0 ; i<game.maxclients ; i++)
+		ReadClient (f, &game.clients[i]);
+
+	fclose (f);
+}
+
+//==========================================================
+
+
+/*
+==============
+WriteEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+	edict_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = *ent;
+
+	// change the pointers to lengths or indexes
+	for (field=fields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=fields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)ent);
+	}
+
+}
+
+/*
+==============
+WriteLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void WriteLevelLocals (FILE *f)
+{
+	field_t		*field;
+	level_locals_t		temp;
+
+	// all of the ints, floats, and vectors stay as they are
+	temp = level;
+
+	// change the pointers to lengths or indexes
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField1 (f, field, (byte *)&temp);
+	}
+
+	// write the block
+	fwrite (&temp, sizeof(temp), 1, f);
+
+	// now write any allocated data following the edict
+	for (field=levelfields ; field->name ; field++)
+	{
+		WriteField2 (f, field, (byte *)&level);
+	}
+}
+
+
+/*
+==============
+ReadEdict
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadEdict (FILE *f, edict_t *ent)
+{
+	field_t		*field;
+
+	fread (ent, sizeof(*ent), 1, f);
+
+	for (field=fields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)ent);
+	}
+}
+
+/*
+==============
+ReadLevelLocals
+
+All pointer variables (except function pointers) must be handled specially.
+==============
+*/
+void ReadLevelLocals (FILE *f)
+{
+	field_t		*field;
+
+	fread (&level, sizeof(level), 1, f);
+
+	for (field=levelfields ; field->name ; field++)
+	{
+		ReadField (f, field, (byte *)&level);
+	}
+}
+
+/*
+=================
+WriteLevel
+
+=================
+*/
+void WriteLevel (char *filename)
+{
+	int		i;
+	edict_t	*ent;
+	FILE	*f;
+	void	*base;
+
+	f = fopen (filename, "wb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// write out edict size for checking
+	i = sizeof(edict_t);
+	fwrite (&i, sizeof(i), 1, f);
+
+	// write out a function pointer for checking
+	base = (void *)InitGame;
+	fwrite (&base, sizeof(base), 1, f);
+
+	// write out level_locals_t
+	WriteLevelLocals (f);
+
+	// write out all the entities
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+		if (!ent->inuse)
+			continue;
+		fwrite (&i, sizeof(i), 1, f);
+		WriteEdict (f, ent);
+	}
+	i = -1;
+	fwrite (&i, sizeof(i), 1, f);
+
+	fclose (f);
+}
+
+
+/*
+=================
+ReadLevel
+
+SpawnEntities will allready have been called on the
+level the same way it was when the level was saved.
+
+That is necessary to get the baselines
+set up identically.
+
+The server will have cleared all of the world links before
+calling ReadLevel.
+
+No clients are connected yet.
+=================
+*/
+void ReadLevel (char *filename)
+{
+	int		entnum;
+	FILE	*f;
+	int		i;
+	void	*base;
+	edict_t	*ent;
+
+	f = fopen (filename, "rb");
+	if (!f)
+		gi.error ("Couldn't open %s", filename);
+
+	// free any dynamic memory allocated by loading the level
+	// base state
+	gi.FreeTags (TAG_LEVEL);
+
+	// wipe all the entities
+	memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0]));
+	globals.num_edicts = maxclients->value+1;
+
+	// check edict size
+	fread (&i, sizeof(i), 1, f);
+	if (i != sizeof(edict_t))
+	{
+		fclose (f);
+		gi.error ("ReadLevel: mismatched edict size");
+	}
+
+	// check function pointer base address
+	fread (&base, sizeof(base), 1, f);
+#ifdef _WIN32
+	if (base != (void *)InitGame)
+	{
+		fclose (f);
+		gi.error ("ReadLevel: function pointers have moved");
+	}
+#else
+	gi.dprintf("Function offsets %d\n", ((byte *)base) - ((byte *)InitGame));
+#endif
+
+	// load the level locals
+	ReadLevelLocals (f);
+
+	// load all the entities
+	while (1)
+	{
+		if (fread (&entnum, sizeof(entnum), 1, f) != 1)
+		{
+			fclose (f);
+			gi.error ("ReadLevel: failed to read entnum");
+		}
+		if (entnum == -1)
+			break;
+		if (entnum >= globals.num_edicts)
+			globals.num_edicts = entnum+1;
+
+		ent = &g_edicts[entnum];
+		ReadEdict (f, ent);
+
+		// let the server rebuild world links for this ent
+		memset (&ent->area, 0, sizeof(ent->area));
+		gi.linkentity (ent);
+	}
+
+	fclose (f);
+
+	// mark all clients as unconnected
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		ent = &g_edicts[i+1];
+		ent->client = game.clients + i;
+		ent->client->pers.connected = false;
+	}
+
+	// do any load time things at this point
+	for (i=0 ; i<globals.num_edicts ; i++)
+	{
+		ent = &g_edicts[i];
+
+		if (!ent->inuse)
+			continue;
+
+		// fire any cross-level triggers
+		if (ent->classname)
+			if (strcmp(ent->classname, "target_crosslevel_target") == 0)
+				ent->nextthink = level.time + ent->delay;
+	}
+}
--- /dev/null
+++ b/xatrix/g_spawn.c
@@ -1,0 +1,1023 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+typedef struct
+{
+	char	*name;
+	void	(*spawn)(edict_t *ent);
+} spawn_t;
+
+
+void SP_item_health (edict_t *self);
+void SP_item_health_small (edict_t *self);
+void SP_item_health_large (edict_t *self);
+void SP_item_health_mega (edict_t *self);
+
+void SP_info_player_start (edict_t *ent);
+void SP_info_player_deathmatch (edict_t *ent);
+void SP_info_player_coop (edict_t *ent);
+void SP_info_player_intermission (edict_t *ent);
+
+void SP_func_plat (edict_t *ent);
+void SP_func_rotating (edict_t *ent);
+void SP_func_button (edict_t *ent);
+void SP_func_door (edict_t *ent);
+void SP_func_door_secret (edict_t *ent);
+void SP_func_door_rotating (edict_t *ent);
+void SP_func_water (edict_t *ent);
+void SP_func_train (edict_t *ent);
+void SP_func_conveyor (edict_t *self);
+void SP_func_wall (edict_t *self);
+void SP_func_object (edict_t *self);
+void SP_func_explosive (edict_t *self);
+void SP_func_timer (edict_t *self);
+void SP_func_areaportal (edict_t *ent);
+void SP_func_clock (edict_t *ent);
+void SP_func_killbox (edict_t *ent);
+
+void SP_trigger_always (edict_t *ent);
+void SP_trigger_once (edict_t *ent);
+void SP_trigger_multiple (edict_t *ent);
+void SP_trigger_relay (edict_t *ent);
+void SP_trigger_push (edict_t *ent);
+void SP_trigger_hurt (edict_t *ent);
+void SP_trigger_key (edict_t *ent);
+void SP_trigger_counter (edict_t *ent);
+void SP_trigger_elevator (edict_t *ent);
+void SP_trigger_gravity (edict_t *ent);
+void SP_trigger_monsterjump (edict_t *ent);
+
+void SP_target_temp_entity (edict_t *ent);
+void SP_target_speaker (edict_t *ent);
+void SP_target_explosion (edict_t *ent);
+void SP_target_changelevel (edict_t *ent);
+void SP_target_secret (edict_t *ent);
+void SP_target_goal (edict_t *ent);
+void SP_target_splash (edict_t *ent);
+void SP_target_spawner (edict_t *ent);
+void SP_target_blaster (edict_t *ent);
+void SP_target_crosslevel_trigger (edict_t *ent);
+void SP_target_crosslevel_target (edict_t *ent);
+void SP_target_laser (edict_t *self);
+void SP_target_help (edict_t *ent);
+void SP_target_actor (edict_t *ent);
+void SP_target_lightramp (edict_t *self);
+void SP_target_earthquake (edict_t *ent);
+void SP_target_character (edict_t *ent);
+void SP_target_string (edict_t *ent);
+
+void SP_worldspawn (edict_t *ent);
+void SP_viewthing (edict_t *ent);
+
+void SP_light (edict_t *self);
+void SP_light_mine1 (edict_t *ent);
+void SP_light_mine2 (edict_t *ent);
+void SP_info_null (edict_t *self);
+void SP_info_notnull (edict_t *self);
+void SP_path_corner (edict_t *self);
+void SP_point_combat (edict_t *self);
+
+void SP_misc_explobox (edict_t *self);
+void SP_misc_banner (edict_t *self);
+void SP_misc_satellite_dish (edict_t *self);
+void SP_misc_actor (edict_t *self);
+void SP_misc_gib_arm (edict_t *self);
+void SP_misc_gib_leg (edict_t *self);
+void SP_misc_gib_head (edict_t *self);
+void SP_misc_insane (edict_t *self);
+void SP_misc_deadsoldier (edict_t *self);
+void SP_misc_viper (edict_t *self);
+void SP_misc_viper_bomb (edict_t *self);
+void SP_misc_bigviper (edict_t *self);
+void SP_misc_strogg_ship (edict_t *self);
+void SP_misc_teleporter (edict_t *self);
+void SP_misc_teleporter_dest (edict_t *self);
+void SP_misc_blackhole (edict_t *self);
+void SP_misc_eastertank (edict_t *self);
+void SP_misc_easterchick (edict_t *self);
+void SP_misc_easterchick2 (edict_t *self);
+
+void SP_monster_berserk (edict_t *self);
+void SP_monster_gladiator (edict_t *self);
+void SP_monster_gunner (edict_t *self);
+void SP_monster_infantry (edict_t *self);
+void SP_monster_soldier_light (edict_t *self);
+void SP_monster_soldier (edict_t *self);
+void SP_monster_soldier_ss (edict_t *self);
+void SP_monster_tank (edict_t *self);
+void SP_monster_medic (edict_t *self);
+void SP_monster_flipper (edict_t *self);
+void SP_monster_chick (edict_t *self);
+void SP_monster_parasite (edict_t *self);
+void SP_monster_flyer (edict_t *self);
+void SP_monster_brain (edict_t *self);
+void SP_monster_floater (edict_t *self);
+void SP_monster_hover (edict_t *self);
+void SP_monster_mutant (edict_t *self);
+void SP_monster_supertank (edict_t *self);
+void SP_monster_boss2 (edict_t *self);
+void SP_monster_jorg (edict_t *self);
+void SP_monster_boss3_stand (edict_t *self);
+
+void SP_monster_commander_body (edict_t *self);
+
+void SP_turret_breach (edict_t *self);
+void SP_turret_base (edict_t *self);
+void SP_turret_driver (edict_t *self);
+
+// RAFAEL 14-APR-98
+void SP_monster_soldier_hypergun (edict_t *self);
+void SP_monster_soldier_lasergun (edict_t *self);
+void SP_monster_soldier_ripper (edict_t *self);
+void SP_monster_fixbot (edict_t *self);
+void SP_monster_gekk (edict_t *self);
+void SP_monster_chick_heat (edict_t *self);
+void SP_monster_gladb (edict_t *self);
+void SP_monster_boss5 (edict_t *self);
+void SP_rotating_light (edict_t *self);
+void SP_object_repair (edict_t *self);
+void SP_misc_crashviper (edict_t *ent);
+void SP_misc_viper_missile (edict_t *self);
+void SP_misc_amb4 (edict_t *ent);
+void SP_target_mal_laser (edict_t *ent);
+void SP_misc_transport (edict_t *ent);
+// END 14-APR-98
+
+void SP_misc_nuke (edict_t *ent);
+
+
+spawn_t	spawns[] = {
+	{"item_health", SP_item_health},
+	{"item_health_small", SP_item_health_small},
+	{"item_health_large", SP_item_health_large},
+	{"item_health_mega", SP_item_health_mega},
+
+	{"info_player_start", SP_info_player_start},
+	{"info_player_deathmatch", SP_info_player_deathmatch},
+	{"info_player_coop", SP_info_player_coop},
+	{"info_player_intermission", SP_info_player_intermission},
+
+	{"func_plat", SP_func_plat},
+	{"func_button", SP_func_button},
+	{"func_door", SP_func_door},
+	{"func_door_secret", SP_func_door_secret},
+	{"func_door_rotating", SP_func_door_rotating},
+	{"func_rotating", SP_func_rotating},
+	{"func_train", SP_func_train},
+	{"func_water", SP_func_water},
+	{"func_conveyor", SP_func_conveyor},
+	{"func_areaportal", SP_func_areaportal},
+	{"func_clock", SP_func_clock},
+	{"func_wall", SP_func_wall},
+	{"func_object", SP_func_object},
+	{"func_timer", SP_func_timer},
+	{"func_explosive", SP_func_explosive},
+	{"func_killbox", SP_func_killbox},
+
+	// RAFAEL
+	{"func_object_repair", SP_object_repair},
+	{"rotating_light", SP_rotating_light},
+
+	{"trigger_always", SP_trigger_always},
+	{"trigger_once", SP_trigger_once},
+	{"trigger_multiple", SP_trigger_multiple},
+	{"trigger_relay", SP_trigger_relay},
+	{"trigger_push", SP_trigger_push},
+	{"trigger_hurt", SP_trigger_hurt},
+	{"trigger_key", SP_trigger_key},
+	{"trigger_counter", SP_trigger_counter},
+	{"trigger_elevator", SP_trigger_elevator},
+	{"trigger_gravity", SP_trigger_gravity},
+	{"trigger_monsterjump", SP_trigger_monsterjump},
+
+	{"target_temp_entity", SP_target_temp_entity},
+	{"target_speaker", SP_target_speaker},
+	{"target_explosion", SP_target_explosion},
+	{"target_changelevel", SP_target_changelevel},
+	{"target_secret", SP_target_secret},
+	{"target_goal", SP_target_goal},
+	{"target_splash", SP_target_splash},
+	{"target_spawner", SP_target_spawner},
+	{"target_blaster", SP_target_blaster},
+	{"target_crosslevel_trigger", SP_target_crosslevel_trigger},
+	{"target_crosslevel_target", SP_target_crosslevel_target},
+	{"target_laser", SP_target_laser},
+	{"target_help", SP_target_help},
+	{"target_actor", SP_target_actor},
+	{"target_lightramp", SP_target_lightramp},
+	{"target_earthquake", SP_target_earthquake},
+	{"target_character", SP_target_character},
+	{"target_string", SP_target_string},
+
+	// RAFAEL 15-APR-98
+	{"target_mal_laser", SP_target_mal_laser},
+
+
+	{"worldspawn", SP_worldspawn},
+	{"viewthing", SP_viewthing},
+
+	{"light", SP_light},
+	{"light_mine1", SP_light_mine1},
+	{"light_mine2", SP_light_mine2},
+	{"info_null", SP_info_null},
+	{"func_group", SP_info_null},
+	{"info_notnull", SP_info_notnull},
+	{"path_corner", SP_path_corner},
+	{"point_combat", SP_point_combat},
+
+	{"misc_explobox", SP_misc_explobox},
+	{"misc_banner", SP_misc_banner},
+	{"misc_satellite_dish", SP_misc_satellite_dish},
+	{"misc_actor", SP_misc_actor},
+	{"misc_gib_arm", SP_misc_gib_arm},
+	{"misc_gib_leg", SP_misc_gib_leg},
+	{"misc_gib_head", SP_misc_gib_head},
+	{"misc_insane", SP_misc_insane},
+	{"misc_deadsoldier", SP_misc_deadsoldier},
+	{"misc_viper", SP_misc_viper},
+	{"misc_viper_bomb", SP_misc_viper_bomb},
+	{"misc_bigviper", SP_misc_bigviper},
+	{"misc_strogg_ship", SP_misc_strogg_ship},
+	{"misc_teleporter", SP_misc_teleporter},
+	{"misc_teleporter_dest", SP_misc_teleporter_dest},
+	{"misc_blackhole", SP_misc_blackhole},
+	{"misc_eastertank", SP_misc_eastertank},
+	{"misc_easterchick", SP_misc_easterchick},
+	{"misc_easterchick2", SP_misc_easterchick2},
+	// RAFAEL
+	{"misc_crashviper", SP_misc_crashviper},
+	{"misc_viper_missile", SP_misc_viper_missile},
+	{"misc_amb4", SP_misc_amb4},
+	// RAFAEL 17-APR-98
+	{"misc_transport", SP_misc_transport},
+	// END 17-APR-98
+	// RAFAEL 12-MAY-98
+	{"misc_nuke", SP_misc_nuke},
+	
+	{"monster_berserk", SP_monster_berserk},
+	{"monster_gladiator", SP_monster_gladiator},
+	{"monster_gunner", SP_monster_gunner},
+	{"monster_infantry", SP_monster_infantry},
+	{"monster_soldier_light", SP_monster_soldier_light},
+	{"monster_soldier", SP_monster_soldier},
+	{"monster_soldier_ss", SP_monster_soldier_ss},
+	{"monster_tank", SP_monster_tank},
+	{"monster_tank_commander", SP_monster_tank},
+	{"monster_medic", SP_monster_medic},
+	{"monster_flipper", SP_monster_flipper},
+	{"monster_chick", SP_monster_chick},
+	{"monster_parasite", SP_monster_parasite},
+	{"monster_flyer", SP_monster_flyer},
+	{"monster_brain", SP_monster_brain},
+	{"monster_floater", SP_monster_floater},
+	{"monster_hover", SP_monster_hover},
+	{"monster_mutant", SP_monster_mutant},
+	{"monster_supertank", SP_monster_supertank},
+	{"monster_boss2", SP_monster_boss2},
+	{"monster_boss3_stand", SP_monster_boss3_stand},
+	{"monster_jorg", SP_monster_jorg},
+
+	{"monster_commander_body", SP_monster_commander_body},
+
+	// RAFAEL 14-APR-98
+	{"monster_soldier_hypergun", SP_monster_soldier_hypergun},
+	{"monster_soldier_lasergun", SP_monster_soldier_lasergun},
+	{"monster_soldier_ripper",	SP_monster_soldier_ripper},
+	{"monster_fixbot", SP_monster_fixbot},
+	{"monster_gekk", SP_monster_gekk},
+	{"monster_chick_heat", SP_monster_chick_heat},
+	{"monster_gladb", SP_monster_gladb},
+	{"monster_boss5", SP_monster_boss5},
+	// END 14-APR-98
+	
+	{"turret_breach", SP_turret_breach},
+	{"turret_base", SP_turret_base},
+	{"turret_driver", SP_turret_driver},
+
+	{NULL, NULL}
+};
+
+/*
+===============
+ED_CallSpawn
+
+Finds the spawn function for the entity and calls it
+===============
+*/
+void ED_CallSpawn (edict_t *ent)
+{
+	spawn_t	*s;
+	gitem_t	*item;
+	int		i;
+
+	if (!ent->classname)
+	{
+		gi.dprintf ("ED_CallSpawn: NULL classname\n");
+		return;
+	}
+
+	// check item spawn functions
+	for (i=0,item=itemlist ; i<game.num_items ; i++,item++)
+	{
+		if (!item->classname)
+			continue;
+		if (!strcmp(item->classname, ent->classname))
+		{	// found it
+			SpawnItem (ent, item);
+			return;
+		}
+	}
+
+	// check normal spawn functions
+	for (s=spawns ; s->name ; s++)
+	{
+		if (!strcmp(s->name, ent->classname))
+		{	// found it
+			s->spawn (ent);
+			return;
+		}
+	}
+	gi.dprintf ("%s doesn't have a spawn function\n", ent->classname);
+}
+
+/*
+=============
+ED_NewString
+=============
+*/
+char *ED_NewString (char *string)
+{
+	char	*newb, *new_p;
+	int		i,l;
+	
+	l = strlen(string) + 1;
+
+	newb = gi.TagMalloc (l, TAG_LEVEL);
+
+	new_p = newb;
+
+	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;
+}
+
+
+
+
+/*
+===============
+ED_ParseField
+
+Takes a key/value pair and sets the binary values
+in an edict
+===============
+*/
+void ED_ParseField (char *key, char *value, edict_t *ent)
+{
+	field_t	*f;
+	byte	*b;
+	float	v;
+	vec3_t	vec;
+
+	for (f=fields ; f->name ; f++)
+	{
+		if (!(f->flags & FFL_NOSPAWN) && !cistrcmp(f->name, key))
+		{	// found it
+			if (f->flags & FFL_SPAWNTEMP)
+				b = (byte *)&st;
+			else
+				b = (byte *)ent;
+
+			switch (f->type)
+			{
+			case F_LSTRING:
+				*(char **)(b+f->ofs) = ED_NewString (value);
+				break;
+			case F_VECTOR:
+				sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]);
+				((float *)(b+f->ofs))[0] = vec[0];
+				((float *)(b+f->ofs))[1] = vec[1];
+				((float *)(b+f->ofs))[2] = vec[2];
+				break;
+			case F_INT:
+				*(int *)(b+f->ofs) = atoi(value);
+				break;
+			case F_FLOAT:
+				*(float *)(b+f->ofs) = atof(value);
+				break;
+			case F_ANGLEHACK:
+				v = atof(value);
+				((float *)(b+f->ofs))[0] = 0;
+				((float *)(b+f->ofs))[1] = v;
+				((float *)(b+f->ofs))[2] = 0;
+				break;
+			case F_IGNORE:
+				break;
+			}
+			return;
+		}
+	}
+	gi.dprintf ("%s is not a field\n", key);
+}
+
+/*
+====================
+ED_ParseEdict
+
+Parses an edict out of the given string, returning the new position
+ed should be a properly initialized empty edict.
+====================
+*/
+char *ED_ParseEdict (char *data, edict_t *ent)
+{
+	qboolean	init;
+	char		keyname[256];
+	char		*com_token;
+
+	init = false;
+	memset (&st, 0, sizeof(st));
+
+// go through all the dictionary pairs
+	while (1)
+	{	
+	// parse key
+		com_token = COM_Parse (&data);
+		if (com_token[0] == '}')
+			break;
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		strncpy (keyname, com_token, sizeof(keyname)-1);
+		
+	// parse value	
+		com_token = COM_Parse (&data);
+		if (!data)
+			gi.error ("ED_ParseEntity: EOF without closing brace");
+
+		if (com_token[0] == '}')
+			gi.error ("ED_ParseEntity: closing brace without data");
+
+		init = true;	
+
+	// keynames with a leading underscore are used for utility comments,
+	// and are immediately discarded by quake
+		if (keyname[0] == '_')
+			continue;
+
+		ED_ParseField (keyname, com_token, ent);
+	}
+
+	if (!init)
+		memset (ent, 0, sizeof(*ent));
+
+	return data;
+}
+
+
+/*
+================
+G_FindTeams
+
+Chain together all entities with a matching team field.
+
+All but the first will have the FL_TEAMSLAVE flag set.
+All but the last will have the teamchain field set to the next one
+================
+*/
+void G_FindTeams (void)
+{
+	edict_t	*e, *e2, *chain;
+	int		i, j;
+	int		c, c2;
+
+	c = 0;
+	c2 = 0;
+	for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->team)
+			continue;
+		if (e->flags & FL_TEAMSLAVE)
+			continue;
+		chain = e;
+		e->teammaster = e;
+		c++;
+		c2++;
+		for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++)
+		{
+			if (!e2->inuse)
+				continue;
+			if (!e2->team)
+				continue;
+			if (e2->flags & FL_TEAMSLAVE)
+				continue;
+			if (!strcmp(e->team, e2->team))
+			{
+				c2++;
+				chain->teamchain = e2;
+				e2->teammaster = e;
+				chain = e2;
+				e2->flags |= FL_TEAMSLAVE;
+			}
+		}
+	}
+
+	gi.dprintf ("%i teams with %i entities\n", c, c2);
+}
+
+/*
+==============
+SpawnEntities
+
+Creates a server's entity / program execution context by
+parsing textual entity definitions out of an ent file.
+==============
+*/
+void SpawnEntities (char *mapname, char *entities, char *spawnpoint)
+{
+	edict_t		*ent;
+	int			inhibit;
+	char		*com_token;
+	int			i;
+	float		skill_level;
+
+	skill_level = floor (skill->value);
+	if (skill_level < 0)
+		skill_level = 0;
+	if (skill_level > 3)
+		skill_level = 3;
+	if (skill->value != skill_level)
+		gi.cvar_forceset("skill", va("%f", skill_level));
+
+	SaveClientData ();
+
+	gi.FreeTags (TAG_LEVEL);
+
+	memset (&level, 0, sizeof(level));
+	memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0]));
+
+	strncpy (level.mapname, mapname, sizeof(level.mapname)-1);
+	strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1);
+
+	// set client fields on player ents
+	for (i=0 ; i<game.maxclients ; i++)
+		g_edicts[i+1].client = game.clients + i;
+
+	ent = NULL;
+	inhibit = 0;
+
+// parse ents
+	while (1)
+	{
+		// parse the opening brace	
+		com_token = COM_Parse (&entities);
+		if (!entities)
+			break;
+		if (com_token[0] != '{')
+			gi.error ("ED_LoadFromFile: found %s when expecting {",com_token);
+
+		if (!ent)
+			ent = g_edicts;
+		else
+			ent = G_Spawn ();
+		entities = ED_ParseEdict (entities, ent);
+
+		// yet another map hack
+		if (!cistrcmp(level.mapname, "command") && !cistrcmp(ent->classname, "trigger_once") && !cistrcmp(ent->model, "*27"))
+			ent->spawnflags &= ~SPAWNFLAG_NOT_HARD;
+
+		// remove things (except the world) from different skill levels or deathmatch
+		if (ent != g_edicts)
+		{
+			if (deathmatch->value)
+			{
+				if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH )
+				{
+					G_FreeEdict (ent);	
+					inhibit++;
+					continue;
+				}
+			}
+			else
+			{
+				if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */
+					((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) ||
+					((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) ||
+					(((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))
+					)
+					{
+						G_FreeEdict (ent);	
+						inhibit++;
+						continue;
+					}
+			}
+
+			ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH);
+		}
+
+		ED_CallSpawn (ent);
+	}	
+
+	gi.dprintf ("%i entities inhibited\n", inhibit);
+
+#ifdef DEBUG
+	i = 1;
+	ent = EDICT_NUM(i);
+	while (i < globals.num_edicts) {
+		if (ent->inuse != 0 || ent->inuse != 1)
+			Com_DPrintf("Invalid entity %d\n", i);
+		i++, ent++;
+	}
+#endif
+
+	G_FindTeams ();
+
+	PlayerTrail_Init ();
+}
+
+
+//===================================================================
+
+/*
+	// cursor positioning
+	xl <value>
+	xr <value>
+	yb <value>
+	yt <value>
+	xv <value>
+	yv <value>
+
+	// drawing
+	statpic <name>
+	pic <stat>
+	num <fieldwidth> <stat>
+	string <stat>
+
+	// control
+	if <stat>
+	ifeq <stat> <value>
+	ifbit <stat> <value>
+	endif
+
+*/
+
+char *single_statusbar = 
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	262 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+;
+
+char *dm_statusbar =
+"yb	-24 "
+
+// health
+"xv	0 "
+"hnum "
+"xv	50 "
+"pic 0 "
+
+// ammo
+"if 2 "
+"	xv	100 "
+"	anum "
+"	xv	150 "
+"	pic 2 "
+"endif "
+
+// armor
+"if 4 "
+"	xv	200 "
+"	rnum "
+"	xv	250 "
+"	pic 4 "
+"endif "
+
+// selected item
+"if 6 "
+"	xv	296 "
+"	pic 6 "
+"endif "
+
+"yb	-50 "
+
+// picked up item
+"if 7 "
+"	xv	0 "
+"	pic 7 "
+"	xv	26 "
+"	yb	-42 "
+"	stat_string 8 "
+"	yb	-50 "
+"endif "
+
+// timer
+"if 9 "
+"	xv	246 "
+"	num	2	10 "
+"	xv	296 "
+"	pic	9 "
+"endif "
+
+//  help / weapon icon 
+"if 11 "
+"	xv	148 "
+"	pic	11 "
+"endif "
+
+//  frags
+"xr	-50 "
+"yt 2 "
+"num 3 14 "
+
+// spectator
+"if 17 "
+  "xv 0 "
+  "yb -58 "
+  "string2 \"SPECTATOR MODE\" "
+"endif "
+
+// chase camera
+"if 16 "
+  "xv 0 "
+  "yb -68 "
+  "string \"Chasing\" "
+  "xv 64 "
+  "stat_string 16 "
+"endif "
+;
+
+
+/*QUAKED worldspawn (0 0 0) ?
+
+Only used for the world.
+"sky"	environment map name
+"skyaxis"	vector axis for rotating sky
+"skyrotate"	speed of rotation in degrees/second
+"sounds"	music cd track number
+"gravity"	800 is default gravity
+"message"	text to print at user logon
+*/
+void SP_worldspawn (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_PUSH;
+	ent->solid = SOLID_BSP;
+	ent->inuse = true;			// since the world doesn't use G_Spawn()
+	ent->s.modelindex = 1;		// world model is always index 1
+
+	//---------------
+
+	// reserve some spots for dead player bodies for coop / deathmatch
+	InitBodyQue ();
+
+	// set configstrings for items
+	SetItemNames ();
+
+	if (st.nextmap)
+		strcpy (level.nextmap, st.nextmap);
+
+	// make some data visible to the server
+
+	if (ent->message && ent->message[0])
+	{
+		gi.configstring (CS_NAME, ent->message);
+		strncpy (level.level_name, ent->message, sizeof(level.level_name));
+	}
+	else
+		strncpy (level.level_name, level.mapname, sizeof(level.level_name));
+
+	if (st.sky && st.sky[0])
+		gi.configstring (CS_SKY, st.sky);
+	else
+		gi.configstring (CS_SKY, "unit1_");
+
+	gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) );
+
+	gi.configstring (CS_SKYAXIS, va("%f %f %f",
+		st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) );
+
+	gi.configstring (CS_CDTRACK, va("%i", ent->sounds) );
+
+	gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) );
+
+	// status bar program
+	if (deathmatch->value)
+		gi.configstring (CS_STATUSBAR, dm_statusbar);
+	else
+		gi.configstring (CS_STATUSBAR, single_statusbar);
+
+	//---------------
+
+
+	// help icon for statusbar
+	gi.imageindex ("i_help");
+	level.pic_health = gi.imageindex ("i_health");
+	gi.imageindex ("help");
+	gi.imageindex ("field_3");
+
+	if (!st.gravity)
+		gi.cvar_set("sv_gravity", "800");
+	else
+		gi.cvar_set("sv_gravity", st.gravity);
+
+	snd_fry = gi.soundindex ("player/fry.wav");	// standing in lava / slime
+
+	PrecacheItem (FindItem ("Blaster"));
+
+	gi.soundindex ("player/lava1.wav");
+	gi.soundindex ("player/lava2.wav");
+
+	gi.soundindex ("misc/pc_up.wav");
+	gi.soundindex ("misc/talk1.wav");
+
+	gi.soundindex ("misc/udeath.wav");
+
+	// gibs
+	gi.soundindex ("items/respawn1.wav");
+
+	// sexed sounds
+	gi.soundindex ("*death1.wav");
+	gi.soundindex ("*death2.wav");
+	gi.soundindex ("*death3.wav");
+	gi.soundindex ("*death4.wav");
+	gi.soundindex ("*fall1.wav");
+	gi.soundindex ("*fall2.wav");	
+	gi.soundindex ("*gurp1.wav");		// drowning damage
+	gi.soundindex ("*gurp2.wav");	
+	gi.soundindex ("*jump1.wav");		// player jump
+	gi.soundindex ("*pain25_1.wav");
+	gi.soundindex ("*pain25_2.wav");
+	gi.soundindex ("*pain50_1.wav");
+	gi.soundindex ("*pain50_2.wav");
+	gi.soundindex ("*pain75_1.wav");
+	gi.soundindex ("*pain75_2.wav");
+	gi.soundindex ("*pain100_1.wav");
+	gi.soundindex ("*pain100_2.wav");
+
+	// sexed models
+	// THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+	// you can add more, max 19 (pete change)
+	// these models are only loaded in coop or deathmatch. not singleplayer.
+	if (coop->value || deathmatch->value)
+	{
+		gi.modelindex ("#w_blaster.md2");
+		gi.modelindex ("#w_shotgun.md2");
+		gi.modelindex ("#w_sshotgun.md2");
+		gi.modelindex ("#w_machinegun.md2");
+		gi.modelindex ("#w_chaingun.md2");
+		gi.modelindex ("#a_grenades.md2");
+		gi.modelindex ("#w_glauncher.md2");
+		gi.modelindex ("#w_rlauncher.md2");
+		gi.modelindex ("#w_hyperblaster.md2");
+		gi.modelindex ("#w_railgun.md2");
+		gi.modelindex ("#w_bfg.md2");
+
+		gi.modelindex ("#w_phalanx.md2");
+		gi.modelindex ("#w_ripper.md2");
+	}
+
+	//-------------------
+
+	gi.soundindex ("player/gasp1.wav");		// gasping for air
+	gi.soundindex ("player/gasp2.wav");		// head breaking surface, not gasping
+
+	gi.soundindex ("player/watr_in.wav");	// feet hitting water
+	gi.soundindex ("player/watr_out.wav");	// feet leaving water
+
+	gi.soundindex ("player/watr_un.wav");	// head going underwater
+	
+	gi.soundindex ("player/u_breath1.wav");
+	gi.soundindex ("player/u_breath2.wav");
+
+	gi.soundindex ("items/pkup.wav");		// bonus item pickup
+	gi.soundindex ("world/land.wav");		// landing thud
+	gi.soundindex ("misc/h2ohit1.wav");		// landing splash
+
+	gi.soundindex ("items/damage.wav");
+	gi.soundindex ("items/protect.wav");
+	gi.soundindex ("items/protect4.wav");
+	gi.soundindex ("weapons/noammo.wav");
+
+	gi.soundindex ("infantry/inflies1.wav");
+
+	sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");
+	gi.modelindex ("models/objects/gibs/arm/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone/tris.md2");
+	gi.modelindex ("models/objects/gibs/bone2/tris.md2");
+	gi.modelindex ("models/objects/gibs/chest/tris.md2");
+	gi.modelindex ("models/objects/gibs/skull/tris.md2");
+	gi.modelindex ("models/objects/gibs/head2/tris.md2");
+
+//
+// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.
+//
+
+	// 0 normal
+	gi.configstring(CS_LIGHTS+0, "m");
+	
+	// 1 FLICKER (first variety)
+	gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo");
+	
+	// 2 SLOW STRONG PULSE
+	gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+	
+	// 3 CANDLE (first variety)
+	gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+	
+	// 4 FAST STROBE
+	gi.configstring(CS_LIGHTS+4, "mamamamamama");
+	
+	// 5 GENTLE PULSE 1
+	gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+	
+	// 6 FLICKER (second variety)
+	gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno");
+	
+	// 7 CANDLE (second variety)
+	gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm");
+	
+	// 8 CANDLE (third variety)
+	gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+	
+	// 9 SLOW STROBE (fourth variety)
+	gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz");
+	
+	// 10 FLUORESCENT FLICKER
+	gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma");
+
+	// 11 SLOW PULSE NOT FADE TO BLACK
+	gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba");
+	
+	// styles 32-62 are assigned by the light program for switchable lights
+
+	// 63 testing
+	gi.configstring(CS_LIGHTS+63, "a");
+}
+
--- /dev/null
+++ b/xatrix/g_svcmds.c
@@ -1,0 +1,284 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void	Svcmd_Test_f (void)
+{
+	gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n");
+}
+
+/*
+==============================================================================
+
+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 any unspecified digits will 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.
+
+writeip
+Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date.  The filter lists are not saved and restored by default, because I beleive it would cause too much confusion.
+
+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.
+
+
+==============================================================================
+*/
+
+typedef struct
+{
+	unsigned	mask;
+	unsigned	compare;
+} ipfilter_t;
+
+#define	MAX_IPFILTERS	1024
+
+ipfilter_t	ipfilters[MAX_IPFILTERS];
+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')
+		{
+			gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s);
+			return false;
+		}
+		
+		j = 0;
+		while (*s >= '0' && *s <= '9')
+		{
+			num[j++] = *s++;
+		}
+		num[j] = 0;
+		b[i] = atoi(num);
+		if (b[i] != 0)
+			m[i] = 255;
+
+		if (!*s)
+			break;
+		s++;
+	}
+	
+	f->mask = *(unsigned *)m;
+	f->compare = *(unsigned *)b;
+	
+	return true;
+}
+
+/*
+=================
+SV_FilterPacket
+=================
+*/
+qboolean SV_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 (int)filterban->value;
+
+	return (int)!filterban->value;
+}
+
+
+/*
+=================
+SV_AddIP_f
+=================
+*/
+void SVCmd_AddIP_f (void)
+{
+	int		i;
+	
+	if (gi.argc() < 3) {
+		gi.cprintf(NULL, PRINT_HIGH, "Usage:  addip <ip-mask>\n");
+		return;
+	}
+
+	for (i=0 ; i<numipfilters ; i++)
+		if (ipfilters[i].compare == 0xffffffff)
+			break;		// free spot
+	if (i == numipfilters)
+	{
+		if (numipfilters == MAX_IPFILTERS)
+		{
+			gi.cprintf (NULL, PRINT_HIGH, "IP filter list is full\n");
+			return;
+		}
+		numipfilters++;
+	}
+	
+	if (!StringToFilter (gi.argv(2), &ipfilters[i]))
+		ipfilters[i].compare = 0xffffffff;
+}
+
+/*
+=================
+SV_RemoveIP_f
+=================
+*/
+void SVCmd_RemoveIP_f (void)
+{
+	ipfilter_t	f;
+	int			i, j;
+
+	if (gi.argc() < 3) {
+		gi.cprintf(NULL, PRINT_HIGH, "Usage:  sv removeip <ip-mask>\n");
+		return;
+	}
+
+	if (!StringToFilter (gi.argv(2), &f))
+		return;
+
+	for (i=0 ; i<numipfilters ; i++)
+		if (ipfilters[i].mask == f.mask
+		&& ipfilters[i].compare == f.compare)
+		{
+			for (j=i+1 ; j<numipfilters ; j++)
+				ipfilters[j-1] = ipfilters[j];
+			numipfilters--;
+			gi.cprintf (NULL, PRINT_HIGH, "Removed.\n");
+			return;
+		}
+	gi.cprintf (NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2));
+}
+
+/*
+=================
+SV_ListIP_f
+=================
+*/
+void SVCmd_ListIP_f (void)
+{
+	int		i;
+	byte	b[4];
+
+	gi.cprintf (NULL, PRINT_HIGH, "Filter list:\n");
+	for (i=0 ; i<numipfilters ; i++)
+	{
+		*(unsigned *)b = ipfilters[i].compare;
+		gi.cprintf (NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]);
+	}
+}
+
+/*
+=================
+SV_WriteIP_f
+=================
+*/
+void SVCmd_WriteIP_f (void)
+{
+	FILE	*f;
+	char	name[MAX_OSPATH];
+	byte	b[4];
+	int		i;
+	cvar_t	*game;
+
+	game = gi.cvar("game", "", 0);
+
+	if (!*game->string)
+		sprintf (name, "%s/listip.cfg", GAMEVERSION);
+	else
+		sprintf (name, "%s/listip.cfg", game->string);
+
+	gi.cprintf (NULL, PRINT_HIGH, "Writing %s.\n", name);
+
+	f = fopen (name, "wb");
+	if (!f)
+	{
+		gi.cprintf (NULL, PRINT_HIGH, "Couldn't open %s\n", name);
+		return;
+	}
+	
+	fprintf(f, "set filterban %d\n", (int)filterban->value);
+
+	for (i=0 ; i<numipfilters ; i++)
+	{
+		*(unsigned *)b = ipfilters[i].compare;
+		fprintf (f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
+	}
+	
+	fclose (f);
+}
+
+/*
+=================
+ServerCommand
+
+ServerCommand will be called when an "sv" command is issued.
+The game can issue gi.argc() / gi.argv() commands to get the rest
+of the parameters
+=================
+*/
+void	ServerCommand (void)
+{
+	char	*cmd;
+
+	cmd = gi.argv(1);
+	if (cistrcmp (cmd, "test") == 0)
+		Svcmd_Test_f ();
+	else if (cistrcmp (cmd, "addip") == 0)
+		SVCmd_AddIP_f ();
+	else if (cistrcmp (cmd, "removeip") == 0)
+		SVCmd_RemoveIP_f ();
+	else if (cistrcmp (cmd, "listip") == 0)
+		SVCmd_ListIP_f ();
+	else if (cistrcmp (cmd, "writeip") == 0)
+		SVCmd_WriteIP_f ();
+	else
+		gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
+}
+
--- /dev/null
+++ b/xatrix/g_target.c
@@ -1,0 +1,888 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
+Fire an origin based temp entity event to the clients.
+"style"		type byte
+*/
+void Use_Target_Tent (edict_t *ent, edict_t *, edict_t *)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (ent->style);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+}
+
+void SP_target_temp_entity (edict_t *ent)
+{
+	ent->use = Use_Target_Tent;
+}
+
+
+//==========================================================
+
+//==========================================================
+
+/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable
+"noise"		wav file to play
+"attenuation"
+-1 = none, send to whole level
+1 = normal fighting sounds
+2 = idle sound level
+3 = ambient sound level
+"volume"	0.0 to 1.0
+
+Normal sounds play each time the target is used.  The reliable flag can be set for crucial voiceovers.
+
+Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off.
+Multiple identical looping sounds will just increase volume without any speed cost.
+*/
+void Use_Target_Speaker (edict_t *ent, edict_t *, edict_t *)
+{
+	int		chan;
+
+	if (ent->spawnflags & 3)
+	{	// looping sound toggles
+		if (ent->s.sound)
+			ent->s.sound = 0;	// turn it off
+		else
+			ent->s.sound = ent->noise_index;	// start it
+	}
+	else
+	{	// normal sound
+		if (ent->spawnflags & 4)
+			chan = CHAN_VOICE|CHAN_RELIABLE;
+		else
+			chan = CHAN_VOICE;
+		// use a positioned_sound, because this entity won't normally be
+		// sent to any clients because it is invisible
+		gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0);
+	}
+}
+
+void SP_target_speaker (edict_t *ent)
+{
+	char	buffer[MAX_QPATH];
+
+	if(!st.noise)
+	{
+		gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
+		return;
+	}
+	if (!strstr (st.noise, ".wav"))
+		Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
+	else
+		strncpy (buffer, st.noise, sizeof(buffer));
+	ent->noise_index = gi.soundindex (buffer);
+
+	if (!ent->volume)
+		ent->volume = 1.0;
+
+	if (!ent->attenuation)
+		ent->attenuation = 1.0;
+	else if (ent->attenuation == -1)	// use -1 so 0 defaults to 1
+		ent->attenuation = 0;
+
+	// check for prestarted looping sound
+	if (ent->spawnflags & 1)
+		ent->s.sound = ent->noise_index;
+
+	ent->use = Use_Target_Speaker;
+
+	// must link the entity so we get areas and clusters so
+	// the server can determine who to send updates to
+	gi.linkentity (ent);
+}
+
+
+//==========================================================
+
+void Use_Target_Help (edict_t *ent, edict_t *, edict_t *)
+{
+	if (ent->spawnflags & 1)
+		strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1);
+	else
+		strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1);
+
+	game.helpchanged++;
+}
+
+/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1
+When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars.
+*/
+void SP_target_help(edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!ent->message)
+	{
+		gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+	ent->use = Use_Target_Help;
+}
+
+//==========================================================
+
+/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a secret found.
+These are single use targets.
+*/
+void use_target_secret (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_secrets++;
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_secret (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_secret;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_secrets++;
+	// map bug hack
+	if (!cistrcmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
+		ent->message = "You have found a secret area.";
+}
+
+//==========================================================
+
+/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
+Counts a goal completed.
+These are single use targets.
+*/
+void use_target_goal (edict_t *ent, edict_t *, edict_t *activator)
+{
+	gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0);
+
+	level.found_goals++;
+
+	if (level.found_goals == level.total_goals)
+		gi.configstring (CS_CDTRACK, "0");
+
+	G_UseTargets (ent, activator);
+	G_FreeEdict (ent);
+}
+
+void SP_target_goal (edict_t *ent)
+{
+	if (deathmatch->value)
+	{	// auto-remove for deathmatch
+		G_FreeEdict (ent);
+		return;
+	}
+
+	ent->use = use_target_goal;
+	if (!st.noise)
+		st.noise = "misc/secret.wav";
+	ent->noise_index = gi.soundindex (st.noise);
+	ent->svflags = SVF_NOCLIENT;
+	level.total_goals++;
+}
+
+//==========================================================
+
+
+/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8)
+Spawns an explosion temporary entity when used.
+
+"delay"		wait this long before going off
+"dmg"		how much radius damage should be done, defaults to 0
+*/
+void target_explosion_explode (edict_t *self)
+{
+	float		save;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+
+	T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE);
+
+	save = self->delay;
+	self->delay = 0;
+	G_UseTargets (self, self->activator);
+	self->delay = save;
+}
+
+void use_target_explosion (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+
+	if (!self->delay)
+	{
+		target_explosion_explode (self);
+		return;
+	}
+
+	self->think = target_explosion_explode;
+	self->nextthink = level.time + self->delay;
+}
+
+void SP_target_explosion (edict_t *ent)
+{
+	ent->use = use_target_explosion;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
+Changes level to "map" when fired
+*/
+void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
+{
+	if (level.intermissiontime)
+		return;		// already activated
+
+	if (!deathmatch->value && !coop->value)
+	{
+		if (g_edicts[1].health <= 0)
+			return;
+	}
+
+	// if noexit, do a ton of damage to other
+	if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != WORLD)
+	{
+		T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
+		return;
+	}
+
+	// if multiplayer, let everyone know who hit the exit
+	if (deathmatch->value)
+	{
+		if (activator && activator->client)
+			gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
+	}
+
+	// if going to a new unit, clear cross triggers
+	if (strstr(self->map, "*"))	
+		game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
+
+	BeginIntermission (self);
+}
+
+void SP_target_changelevel (edict_t *ent)
+{
+	if (!ent->map)
+	{
+		gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
+		G_FreeEdict (ent);
+		return;
+	}
+
+	// ugly hack because *SOMEBODY* screwed up their map
+   if((cistrcmp(level.mapname, "fact1") == 0) && (cistrcmp(ent->map, "fact3") == 0))
+	   ent->map = "fact3$secret1";
+
+	ent->use = use_target_changelevel;
+	ent->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
+Creates a particle splash effect when used.
+
+Set "sounds" to one of the following:
+  1) sparks
+  2) blue water
+  3) brown water
+  4) slime
+  5) lava
+  6) blood
+
+"count"	how many pixels in the splash
+"dmg"	if set, does a radius damage at this location when it splashes
+		useful for lava/sparks
+*/
+
+void use_target_splash (edict_t *self, edict_t *, edict_t *activator)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPLASH);
+	gi.WriteByte (self->count);
+	gi.WritePosition (self->s.origin);
+	gi.WriteDir (self->movedir);
+	gi.WriteByte (self->sounds);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	if (self->dmg)
+		T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH);
+}
+
+void SP_target_splash (edict_t *self)
+{
+	self->use = use_target_splash;
+	G_SetMovedir (self->s.angles, self->movedir);
+
+	if (!self->count)
+		self->count = 32;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) 1 2 3 4 5 6
+Set target to the type of entity you want spawned.
+Useful for spawning monsters and gibs in the factory levels.
+
+For monsters:
+	Set direction to the facing you want it to have.
+
+For gibs:
+	Set direction if you want it moving and
+	speed how fast it should be moving otherwise it
+	will just be dropped
+*/
+void ED_CallSpawn (edict_t *ent);
+
+void use_target_spawner (edict_t *self, edict_t *, edict_t *)
+{
+	edict_t	*ent;
+
+	ent = G_Spawn();
+	ent->classname = self->target;
+	ent->flags = self->flags;
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (self->s.angles, ent->s.angles);
+	ED_CallSpawn (ent);
+	gi.unlinkentity (ent);
+	KillBox (ent);
+	gi.linkentity (ent);
+	if (self->speed)
+		VectorCopy (self->movedir, ent->velocity);
+}
+
+void SP_target_spawner (edict_t *self)
+{
+	self->use = use_target_spawner;
+	self->svflags = SVF_NOCLIENT;
+	if (self->speed)
+	{
+		G_SetMovedir (self->s.angles, self->movedir);
+		VectorScale (self->movedir, self->speed, self->movedir);
+	}
+}
+
+//==========================================================
+
+/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
+Fires a blaster bolt in the set direction when triggered.
+
+dmg		default is 15
+speed	default is 1000
+*/
+
+void use_target_blaster (edict_t *self, edict_t *, edict_t *)
+{
+	/*
+	int effect;
+
+	if (self->spawnflags & 2)
+		effect = 0;
+	else if (self->spawnflags & 1)
+		effect = EF_HYPERBLASTER;
+	else
+		effect = EF_BLASTER;
+	*/
+
+	fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER);
+	gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
+}
+
+void SP_target_blaster (edict_t *self)
+{
+	self->use = use_target_blaster;
+	G_SetMovedir (self->s.angles, self->movedir);
+	self->noise_index = gi.soundindex ("weapons/laser2.wav");
+
+	if (!self->dmg)
+		self->dmg = 15;
+	if (!self->speed)
+		self->speed = 1000;
+
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+//==========================================================
+
+/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit.  It is OK to check multiple triggers.  Message, delay, target, and killtarget also work.
+*/
+void trigger_crosslevel_trigger_use (edict_t *self, edict_t *, edict_t *)
+{
+	game.serverflags |= self->spawnflags;
+	G_FreeEdict (self);
+}
+
+void SP_target_crosslevel_trigger (edict_t *self)
+{
+	self->svflags = SVF_NOCLIENT;
+	self->use = trigger_crosslevel_trigger_use;
+}
+
+/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
+Triggered by a trigger_crosslevel elsewhere within a unit.  If multiple triggers are checked, all must be true.  Delay, target and
+killtarget also work.
+
+"delay"		delay before using targets if the trigger has been activated (default 1)
+*/
+void target_crosslevel_target_think (edict_t *self)
+{
+	if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
+	{
+		G_UseTargets (self, self);
+		G_FreeEdict (self);
+	}
+}
+
+void SP_target_crosslevel_target (edict_t *self)
+{
+	if (! self->delay)
+		self->delay = 1;
+	self->svflags = SVF_NOCLIENT;
+
+	self->think = target_crosslevel_target_think;
+	self->nextthink = level.time + self->delay;
+}
+
+//==========================================================
+
+/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT
+When triggered, fires a laser.  You can either set a target
+or a direction.
+*/
+
+void target_laser_think (edict_t *self)
+{
+	edict_t	*ignore;
+	vec3_t	start;
+	vec3_t	end;
+	trace_t	tr;
+	vec3_t	point;
+	vec3_t	last_movedir;
+	int		count;
+
+	if (self->spawnflags & 0x80000000)
+		count = 8;
+	else
+		count = 4;
+
+	if (self->enemy)
+	{
+		VectorCopy (self->movedir, last_movedir);
+		VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
+		VectorSubtract (point, self->s.origin, self->movedir);
+		VectorNormalize (self->movedir);
+		if (!VectorCompare(self->movedir, last_movedir))
+			self->spawnflags |= 0x80000000;
+	}
+
+	ignore = self;
+	VectorCopy (self->s.origin, start);
+	VectorMA (start, 2048, self->movedir, end);
+	while(1)
+	{
+		tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+
+		if (!tr.ent)
+			break;
+
+		// hurt it if we can
+		if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER))
+			T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
+
+		// if we hit something that's not a monster or player or is immune to lasers, we're done
+		if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		{
+			if (self->spawnflags & 0x80000000)
+			{
+				self->spawnflags &= ~0x80000000;
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (count);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+			}
+			break;
+		}
+
+		ignore = tr.ent;
+		VectorCopy (tr.endpos, start);
+	}
+
+	VectorCopy (tr.endpos, self->s.old_origin);
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+void target_laser_on (edict_t *self)
+{
+	if (!self->activator)
+		self->activator = self;
+	self->spawnflags |= 0x80000001;
+	self->svflags &= ~SVF_NOCLIENT;
+	target_laser_think (self);
+}
+
+void target_laser_off (edict_t *self)
+{
+	self->spawnflags &= ~1;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+}
+
+void target_laser_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	if (self->spawnflags & 1)
+		target_laser_off (self);
+	else
+		target_laser_on (self);
+}
+
+void target_laser_start (edict_t *self)
+{
+	edict_t *ent;
+
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
+	self->s.modelindex = 1;			// must be non-zero
+
+	// set the beam diameter
+	if (self->spawnflags & 64)
+		self->s.frame = 16;
+	else
+		self->s.frame = 4;
+
+	// set the color
+	if (self->spawnflags & 2)
+		self->s.skinnum = 0xf2f2f0f0;
+	else if (self->spawnflags & 4)
+		self->s.skinnum = 0xd0d1d2d3;
+	else if (self->spawnflags & 8)
+		self->s.skinnum = 0xf3f3f1f1;
+	else if (self->spawnflags & 16)
+		self->s.skinnum = 0xdcdddedf;
+	else if (self->spawnflags & 32)
+		self->s.skinnum = 0xe0e1e2e3;
+
+	if (!self->enemy)
+	{
+		if (self->target)
+		{
+			ent = G_Find (NULL, FOFS(targetname), self->target);
+			if (!ent)
+				gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
+			self->enemy = ent;
+		}
+		else
+		{
+			G_SetMovedir (self->s.angles, self->movedir);
+		}
+	}
+	self->use = target_laser_use;
+	self->think = target_laser_think;
+
+	if (!self->dmg)
+		self->dmg = 1;
+
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	gi.linkentity (self);
+
+	if (self->spawnflags & 1)
+		target_laser_on (self);
+	else
+		target_laser_off (self);
+}
+
+void SP_target_laser (edict_t *self)
+{
+	// let everything else get spawned before we start firing
+	self->think = target_laser_start;
+	self->nextthink = level.time + 1;
+}
+
+
+// RAFAEL 15-APR-98
+/*QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT
+Mal's laser
+*/
+void target_mal_laser_on (edict_t *self)
+{
+	if (!self->activator)
+		self->activator = self;
+	self->spawnflags |= 0x80000001;
+	self->svflags &= ~SVF_NOCLIENT;
+	// target_laser_think (self);
+	self->nextthink = level.time + self->wait + self->delay;
+}
+
+void target_mal_laser_off (edict_t *self)
+{
+	self->spawnflags &= ~1;
+	self->svflags |= SVF_NOCLIENT;
+	self->nextthink = 0;
+}
+
+void target_mal_laser_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->activator = activator;
+	if (self->spawnflags & 1)
+		target_mal_laser_off (self);
+	else
+		target_mal_laser_on (self);
+}
+
+void mal_laser_think (edict_t *self)
+{
+	target_laser_think (self);
+	self->nextthink = level.time + self->wait + 0.1;
+	self->spawnflags |= 0x80000000;
+}
+
+void SP_target_mal_laser (edict_t *self)
+{
+	self->movetype = MOVETYPE_NONE;
+	self->solid = SOLID_NOT;
+	self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT;
+	self->s.modelindex = 1;			// must be non-zero
+
+	// set the beam diameter
+	if (self->spawnflags & 64)
+		self->s.frame = 16;
+	else
+		self->s.frame = 4;
+
+	// set the color
+	if (self->spawnflags & 2)
+		self->s.skinnum = 0xf2f2f0f0;
+	else if (self->spawnflags & 4)
+		self->s.skinnum = 0xd0d1d2d3;
+	else if (self->spawnflags & 8)
+		self->s.skinnum = 0xf3f3f1f1;
+	else if (self->spawnflags & 16)
+		self->s.skinnum = 0xdcdddedf;
+	else if (self->spawnflags & 32)
+		self->s.skinnum = 0xe0e1e2e3;
+
+	G_SetMovedir (self->s.angles, self->movedir);
+	
+	if (!self->delay)
+		self->delay = 0.1;
+
+	if (!self->wait)
+		self->wait = 0.1;
+
+	if (!self->dmg)
+		self->dmg = 5;
+
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	
+	self->nextthink = level.time + self->delay;
+	self->think = mal_laser_think;
+
+	self->use = target_mal_laser_use;
+
+	gi.linkentity (self);
+
+	if (self->spawnflags & 1)
+		target_mal_laser_on (self);
+	else
+		target_mal_laser_off (self);
+}
+// END	15-APR-98
+
+//==========================================================
+
+/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
+speed		How many seconds the ramping will take
+message		two letters; starting lightlevel and ending lightlevel
+*/
+
+void target_lightramp_think (edict_t *self)
+{
+	char	style[2];
+
+	style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
+	style[1] = 0;
+	gi.configstring (CS_LIGHTS+self->enemy->style, style);
+
+	if ((level.time - self->timestamp) < self->speed)
+	{
+		self->nextthink = level.time + FRAMETIME;
+	}
+	else if (self->spawnflags & 1)
+	{
+		char	temp;
+
+		temp = self->movedir[0];
+		self->movedir[0] = self->movedir[1];
+		self->movedir[1] = temp;
+		self->movedir[2] *= -1;
+	}
+}
+
+void target_lightramp_use (edict_t *self, edict_t *, edict_t *)
+{
+	if (!self->enemy)
+	{
+		edict_t		*e;
+
+		// check all the targets
+		e = NULL;
+		while (1)
+		{
+			e = G_Find (e, FOFS(targetname), self->target);
+			if (!e)
+				break;
+			if (strcmp(e->classname, "light") != 0)
+			{
+				gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
+				gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
+			}
+			else
+			{
+				self->enemy = e;
+			}
+		}
+
+		if (!self->enemy)
+		{
+			gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
+			G_FreeEdict (self);
+			return;
+		}
+	}
+
+	self->timestamp = level.time;
+	target_lightramp_think (self);
+}
+
+void SP_target_lightramp (edict_t *self)
+{
+	if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
+	{
+		gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->svflags |= SVF_NOCLIENT;
+	self->use = target_lightramp_use;
+	self->think = target_lightramp_think;
+
+	self->movedir[0] = self->message[0] - 'a';
+	self->movedir[1] = self->message[1] - 'a';
+	self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME);
+}
+
+//==========================================================
+
+/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8)
+When triggered, this initiates a level-wide earthquake.
+All players and monsters are affected.
+"speed"		severity of the quake (default:200)
+"count"		duration of the quake (default:5)
+*/
+
+void target_earthquake_think (edict_t *self)
+{
+	int		i;
+	edict_t	*e;
+
+	if (self->last_move_time < level.time)
+	{
+		gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
+		self->last_move_time = level.time + 0.5;
+	}
+
+	for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
+	{
+		if (!e->inuse)
+			continue;
+		if (!e->client)
+			continue;
+		if (!e->groundentity)
+			continue;
+
+		e->groundentity = NULL;
+		e->velocity[0] += crandom()* 150;
+		e->velocity[1] += crandom()* 150;
+		e->velocity[2] = self->speed * (100.0 / e->mass);
+	}
+
+	if (level.time < self->timestamp)
+		self->nextthink = level.time + FRAMETIME;
+}
+
+void target_earthquake_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	self->timestamp = level.time + self->count;
+	self->nextthink = level.time + FRAMETIME;
+	self->activator = activator;
+	self->last_move_time = 0;
+}
+
+void SP_target_earthquake (edict_t *self)
+{
+	if (!self->targetname)
+		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
+
+	if (!self->count)
+		self->count = 5;
+
+	if (!self->speed)
+		self->speed = 200;
+
+	self->svflags |= SVF_NOCLIENT;
+	self->think = target_earthquake_think;
+	self->use = target_earthquake_use;
+
+	self->noise_index = gi.soundindex ("world/quake.wav");
+}
--- /dev/null
+++ b/xatrix/g_trigger.c
@@ -1,0 +1,694 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void InitTrigger (edict_t *self)
+{
+	if (!VectorCompare (self->s.angles, vec3_origin))
+		G_SetMovedir (self->s.angles, self->movedir);
+
+	self->solid = SOLID_TRIGGER;
+	self->movetype = MOVETYPE_NONE;
+	gi.setmodel (self, self->model);
+	self->svflags = SVF_NOCLIENT;
+}
+
+
+// the wait time has passed, so set back up for another activation
+void multi_wait (edict_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 (edict_t *ent)
+{
+	if (ent->nextthink)
+		return;		// already been triggered
+
+	G_UseTargets (ent, ent->activator);
+
+	if (ent->wait > 0)	
+	{
+		ent->think = multi_wait;
+		ent->nextthink = level.time + ent->wait;
+	}
+	else
+	{	// we can't just remove (self) here, because this is a touch function
+		// called while looping through area links...
+		ent->touch = NULL;
+		ent->nextthink = level.time + FRAMETIME;
+		ent->think = G_FreeEdict;
+	}
+}
+
+void Use_Multi (edict_t *ent, edict_t *, edict_t *activator)
+{
+	ent->activator = activator;
+	multi_trigger (ent);
+}
+
+void Touch_Multi (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if(other->client)
+	{
+		if (self->spawnflags & 2)
+			return;
+	}
+	else if (other->svflags & SVF_MONSTER)
+	{
+		if (!(self->spawnflags & 1))
+			return;
+	}
+	else
+		return;
+
+	if (!VectorCompare(self->movedir, vec3_origin))
+	{
+		vec3_t	forward;
+
+		AngleVectors(other->s.angles, forward, NULL, NULL);
+		if (_DotProduct(forward, self->movedir) < 0)
+			return;
+	}
+
+	self->activator = other;
+	multi_trigger (self);
+}
+
+/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
+Variable sized repeatable trigger.  Must be targeted at one or more entities.
+If "delay" is set, the trigger waits some time after activating before firing.
+"wait" : Seconds between triggerings. (.2 default)
+sounds
+1)	secret
+2)	beep beep
+3)	large switch
+4)
+set "message" to text string
+*/
+void trigger_enable (edict_t *self, edict_t *, edict_t *)
+{
+	self->solid = SOLID_TRIGGER;
+	self->use = Use_Multi;
+	gi.linkentity (self);
+}
+
+void SP_trigger_multiple (edict_t *ent)
+{
+	if (ent->sounds == 1)
+		ent->noise_index = gi.soundindex ("misc/secret.wav");
+	else if (ent->sounds == 2)
+		ent->noise_index = gi.soundindex ("misc/talk.wav");
+	else if (ent->sounds == 3)
+		ent->noise_index = gi.soundindex ("misc/trigger1.wav");
+	
+	if (!ent->wait)
+		ent->wait = 0.2;
+	ent->touch = Touch_Multi;
+	ent->movetype = MOVETYPE_NONE;
+	ent->svflags |= SVF_NOCLIENT;
+
+
+	if (ent->spawnflags & 4)
+	{
+		ent->solid = SOLID_NOT;
+		ent->use = trigger_enable;
+	}
+	else
+	{
+		ent->solid = SOLID_TRIGGER;
+		ent->use = Use_Multi;
+	}
+
+	if (!VectorCompare(ent->s.angles, vec3_origin))
+		G_SetMovedir (ent->s.angles, ent->movedir);
+
+	gi.setmodel (ent, ent->model);
+	gi.linkentity (ent);
+}
+
+
+/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED
+Triggers once, then removes itself.
+You must set the key "target" to the name of another object in the level that has a matching "targetname".
+
+If TRIGGERED, this trigger must be triggered before it is live.
+
+sounds
+ 1)	secret
+ 2)	beep beep
+ 3)	large switch
+ 4)
+
+"message"	string to be displayed when triggered
+*/
+
+void SP_trigger_once(edict_t *ent)
+{
+	// make old maps work because I messed up on flag assignments here
+	// triggered was on bit 1 when it should have been on bit 4
+	if (ent->spawnflags & 1)
+	{
+		vec3_t	v;
+
+		VectorMA (ent->mins, 0.5, ent->size, v);
+		ent->spawnflags &= ~1;
+		ent->spawnflags |= 4;
+		gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v));
+	}
+
+	ent->wait = -1;
+	SP_trigger_multiple (ent);
+}
+
+/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This fixed size trigger cannot be touched, it can only be fired by other events.
+*/
+void trigger_relay_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	G_UseTargets (self, activator);
+}
+
+void SP_trigger_relay (edict_t *self)
+{
+	self->use = trigger_relay_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_key
+
+==============================================================================
+*/
+
+/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8)
+A relay trigger that only fires it's targets if player has the proper key.
+Use "item" to specify the required key, for example "key_data_cd"
+*/
+void trigger_key_use (edict_t *self, edict_t *, edict_t *activator)
+{
+	int			index;
+
+	if (!self->item)
+		return;
+	if (!activator->client)
+		return;
+
+	index = ITEM_INDEX(self->item);
+	if (!activator->client->pers.inventory[index])
+	{
+		if (level.time < self->touch_debounce_time)
+			return;
+		self->touch_debounce_time = level.time + 5.0;
+		gi.centerprintf (activator, "You need the %s", self->item->pickup_name);
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0);
+		return;
+	}
+
+	gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0);
+	if (coop->value)
+	{
+		int		player;
+		edict_t	*ent;
+
+		if (strcmp(self->item->classname, "key_power_cube") == 0)
+		{
+			int	cube;
+
+			for (cube = 0; cube < 8; cube++)
+				if (activator->client->pers.power_cubes & (1 << cube))
+					break;
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				if (ent->client->pers.power_cubes & (1 << cube))
+				{
+					ent->client->pers.inventory[index]--;
+					ent->client->pers.power_cubes &= ~(1 << cube);
+				}
+			}
+		}
+		else
+		{
+			for (player = 1; player <= game.maxclients; player++)
+			{
+				ent = &g_edicts[player];
+				if (!ent->inuse)
+					continue;
+				if (!ent->client)
+					continue;
+				ent->client->pers.inventory[index] = 0;
+			}
+		}
+	}
+	else
+	{
+		activator->client->pers.inventory[index]--;
+	}
+
+	G_UseTargets (self, activator);
+
+	self->use = NULL;
+}
+
+void SP_trigger_key (edict_t *self)
+{
+	if (!st.item)
+	{
+		gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin));
+		return;
+	}
+	self->item = FindItemByClassname (st.item);
+
+	if (!self->item)
+	{
+		gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin));
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin));
+		return;
+	}
+
+	gi.soundindex ("misc/keytry.wav");
+	gi.soundindex ("misc/keyuse.wav");
+
+	self->use = trigger_key_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_counter
+
+==============================================================================
+*/
+
+/*QUAKED trigger_counter (.5 .5 .5) ? nomessage
+Acts as an intermediary for an action that takes multiple inputs.
+
+If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
+*/
+
+void trigger_counter_use(edict_t *self, edict_t *, edict_t *activator)
+{
+	if (self->count == 0)
+		return;
+	
+	self->count--;
+
+	if (self->count)
+	{
+		if (! (self->spawnflags & 1))
+		{
+			gi.centerprintf(activator, "%i more to go...", self->count);
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+	
+	if (! (self->spawnflags & 1))
+	{
+		gi.centerprintf(activator, "Sequence completed!");
+		gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+	self->activator = activator;
+	multi_trigger (self);
+}
+
+void SP_trigger_counter (edict_t *self)
+{
+	self->wait = -1;
+	if (!self->count)
+		self->count = 2;
+
+	self->use = trigger_counter_use;
+}
+
+
+/*
+==============================================================================
+
+trigger_always
+
+==============================================================================
+*/
+
+/*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 (edict_t *ent)
+{
+	// we must have some delay to make sure our use targets are present
+	if (ent->delay < 0.2)
+		ent->delay = 0.2;
+	G_UseTargets(ent, ent);
+}
+
+
+/*
+==============================================================================
+
+trigger_push
+
+==============================================================================
+*/
+/*
+#define PUSH_ONCE		1
+
+static int windsound;
+
+void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (strcmp(other->classname, "grenade") == 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+	}
+	else if (other->health > 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+
+		if (other->client)
+		{
+			// don't take falling damage immediately from this
+			VectorCopy (other->velocity, other->client->oldvelocity);
+			if (other->fly_sound_debounce_time < level.time)
+			{
+				other->fly_sound_debounce_time = level.time + 1.5;
+				gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+			}
+		}
+	}
+	if (self->spawnflags & PUSH_ONCE)
+		G_FreeEdict (self);
+}
+
+void SP_trigger_push (edict_t *self)
+{
+	InitTrigger (self);
+	windsound = gi.soundindex ("misc/windfly.wav");
+	self->touch = trigger_push_touch;
+	if (!self->speed)
+		self->speed = 1000;
+	gi.linkentity (self);
+}
+*/
+
+// RAFAEL
+#define PUSH_ONCE  1
+
+static int windsound;
+
+void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (strcmp(other->classname, "grenade") == 0)
+	{
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+	}
+	else if (other->health > 0)
+	{	
+		VectorScale (self->movedir, self->speed * 10, other->velocity);
+
+		if (other->client)
+		{
+			// don't take falling damage immediately from this
+			VectorCopy (other->velocity, other->client->oldvelocity);
+			if (other->fly_sound_debounce_time < level.time)
+			{
+				other->fly_sound_debounce_time = level.time + 1.5;
+				gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
+			}
+		}
+	}
+	if (self->spawnflags & PUSH_ONCE)
+	G_FreeEdict (self);
+}
+
+
+/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_PLUS PUSH_RAMP
+Pushes the player
+"speed"  defaults to 1000
+"wait"  defaults to 10 must use PUSH_PLUS  used for on
+*/
+
+void trigger_push_active (edict_t *self);
+
+void trigger_effect (edict_t *self)
+{
+	vec3_t	origin;
+	vec3_t	size;
+	int		i;
+	
+	VectorScale (self->size, 0.5, size);
+	VectorAdd (self->absmin, size, origin);
+	
+	for (i=0; i<10; i++)
+	{
+		origin[2] += (self->speed * 0.01) * (i + qrandom());
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_TUNNEL_SPARKS);
+		gi.WriteByte (1);
+		gi.WritePosition (origin);
+		gi.WriteDir (vec3_origin);
+		gi.WriteByte (0x74 + (rand()&7));
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+
+}
+
+void trigger_push_inactive (edict_t *self)
+{
+	if (self->delay > level.time)
+	{
+		self->nextthink = level.time + 0.1;
+	}
+	else
+	{
+		self->touch = trigger_push_touch;
+		self->think = trigger_push_active;
+		self->nextthink = level.time + 0.1;
+		self->delay = self->nextthink + self->wait;  
+	}
+}
+
+void trigger_push_active (edict_t *self)
+{
+	if (self->delay > level.time)
+	{
+		self->nextthink = level.time + 0.1;
+		trigger_effect (self);
+	}
+	else
+	{
+		self->touch = NULL;
+		self->think = trigger_push_inactive;
+		self->nextthink = level.time + 0.1;
+		self->delay = self->nextthink + self->wait;  
+	}
+}
+
+void SP_trigger_push (edict_t *self)
+{
+	InitTrigger (self);
+	windsound = gi.soundindex ("misc/windfly.wav");
+	self->touch = trigger_push_touch;
+	
+	if (self->spawnflags & 2)
+	{
+		if (!self->wait)
+			self->wait = 10;
+  
+		self->think = trigger_push_active;
+		self->nextthink = level.time + 0.1;
+		self->delay = self->nextthink + self->wait;
+	}
+
+	if (!self->speed)
+		self->speed = 1000;
+	
+	gi.linkentity (self);
+
+}
+
+/*
+==============================================================================
+
+trigger_hurt
+
+==============================================================================
+*/
+
+/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW
+Any entity that touches this will be hurt.
+
+It does dmg points of damage each server frame
+
+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 (edict_t *self, edict_t *, edict_t *)
+{
+	if (self->solid == SOLID_NOT)
+		self->solid = SOLID_TRIGGER;
+	else
+		self->solid = SOLID_NOT;
+	gi.linkentity (self);
+
+	if (!(self->spawnflags & 2))
+		self->use = NULL;
+}
+
+
+void hurt_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	int		dflags;
+
+	if (!other->takedamage)
+		return;
+
+	if (self->timestamp > level.time)
+		return;
+
+	if (self->spawnflags & 16)
+		self->timestamp = level.time + 1;
+	else
+		self->timestamp = level.time + FRAMETIME;
+
+	if (!(self->spawnflags & 4))
+	{
+		if ((level.framenum % 10) == 0)
+			gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
+	}
+
+	if (self->spawnflags & 8)
+		dflags = DAMAGE_NO_PROTECTION;
+	else
+		dflags = 0;
+	T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT);
+}
+
+void SP_trigger_hurt (edict_t *self)
+{
+	InitTrigger (self);
+
+	self->noise_index = gi.soundindex ("world/electro.wav");
+	self->touch = hurt_touch;
+
+	if (!self->dmg)
+		self->dmg = 5;
+
+	if (self->spawnflags & 1)
+		self->solid = SOLID_NOT;
+	else
+		self->solid = SOLID_TRIGGER;
+
+	if (self->spawnflags & 2)
+		self->use = hurt_use;
+
+	gi.linkentity (self);
+}
+
+
+/*
+==============================================================================
+
+trigger_gravity
+
+==============================================================================
+*/
+
+/*QUAKED trigger_gravity (.5 .5 .5) ?
+Changes the touching entites gravity to
+the value of "gravity".  1.0 is standard
+gravity for the level.
+*/
+
+void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	other->gravity = self->gravity;
+}
+
+void SP_trigger_gravity (edict_t *self)
+{
+	if (st.gravity == 0)
+	{
+		gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin));
+		G_FreeEdict  (self);
+		return;
+	}
+
+	InitTrigger (self);
+	self->gravity = atoi(st.gravity);
+	self->touch = trigger_gravity_touch;
+}
+
+
+/*
+==============================================================================
+
+trigger_monsterjump
+
+==============================================================================
+*/
+
+/*QUAKED trigger_monsterjump (.5 .5 .5) ?
+Walking monsters that touch this will jump in the direction of the trigger's angle
+"speed" default to 200, the speed thrown forward
+"height" default to 200, the speed thrown upwards
+*/
+
+void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (other->flags & (FL_FLY | FL_SWIM) )
+		return;
+	if (other->svflags & SVF_DEADMONSTER)
+		return;
+	if ( !(other->svflags & SVF_MONSTER))
+		return;
+
+// set XY even if not on ground, so the jump will clear lips
+	other->velocity[0] = self->movedir[0] * self->speed;
+	other->velocity[1] = self->movedir[1] * self->speed;
+	
+	if (!other->groundentity)
+		return;
+	
+	other->groundentity = NULL;
+	other->velocity[2] = self->movedir[2];
+}
+
+void SP_trigger_monsterjump (edict_t *self)
+{
+	if (!self->speed)
+		self->speed = 200;
+	if (!st.height)
+		st.height = 200;
+	if (self->s.angles[YAW] == 0)
+		self->s.angles[YAW] = 360;
+	InitTrigger (self);
+	self->touch = trigger_monsterjump_touch;
+	self->movedir[2] = st.height;
+}
+
--- /dev/null
+++ b/xatrix/g_turret.c
@@ -1,0 +1,415 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void AnglesNormalize(vec3_t vec)
+{
+	while(vec[0] > 360)
+		vec[0] -= 360;
+	while(vec[0] < 0)
+		vec[0] += 360;
+	while(vec[1] > 360)
+		vec[1] -= 360;
+	while(vec[1] < 0)
+		vec[1] += 360;
+}
+
+float SnapToEights(float x)
+{
+	x *= 8.0;
+	if (x > 0.0)
+		x += 0.5;
+	else
+		x -= 0.5;
+	return 0.125 * (int)x;
+}
+
+
+void turret_blocked(edict_t *self, edict_t *other)
+{
+	edict_t	*attacker;
+
+	if (other->takedamage)
+	{
+		if (self->teammaster->owner)
+			attacker = self->teammaster->owner;
+		else
+			attacker = self->teammaster;
+		T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
+	}
+}
+
+/*QUAKED turret_breach (0 0 0) ?
+This portion of the turret can change both pitch and yaw.
+The model  should be made with a flat pitch.
+It (and the associated base) need to be oriented towards 0.
+Use "angle" to set the starting angle.
+
+"speed"		default 50
+"dmg"		default 10
+"angle"		point this forward
+"target"	point this at an info_notnull at the muzzle tip
+"minpitch"	min acceptable pitch angle : default -30
+"maxpitch"	max acceptable pitch angle : default 30
+"minyaw"	min acceptable yaw angle   : default 0
+"maxyaw"	max acceptable yaw angle   : default 360
+*/
+
+void turret_breach_fire (edict_t *self)
+{
+	vec3_t	f, r, u;
+	vec3_t	start;
+	int		damage;
+	int		speed;
+
+	AngleVectors (self->s.angles, f, r, u);
+	VectorMA (self->s.origin, self->move_origin[0], f, start);
+	VectorMA (start, self->move_origin[1], r, start);
+	VectorMA (start, self->move_origin[2], u, start);
+
+	damage = 100 + qrandom() * 50;
+	speed = 550 + 50 * skill->value;
+	fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
+	gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
+}
+
+void turret_breach_think (edict_t *self)
+{
+	edict_t	*ent;
+	vec3_t	current_angles;
+	vec3_t	delta;
+
+	VectorCopy (self->s.angles, current_angles);
+	AnglesNormalize(current_angles);
+
+	AnglesNormalize(self->move_angles);
+	if (self->move_angles[PITCH] > 180)
+		self->move_angles[PITCH] -= 360;
+
+	// clamp angles to mins & maxs
+	if (self->move_angles[PITCH] > self->pos1[PITCH])
+		self->move_angles[PITCH] = self->pos1[PITCH];
+	else if (self->move_angles[PITCH] < self->pos2[PITCH])
+		self->move_angles[PITCH] = self->pos2[PITCH];
+
+	if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
+	{
+		float	dmin, dmax;
+
+		dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
+		if (dmin < -180)
+			dmin += 360;
+		else if (dmin > 180)
+			dmin -= 360;
+		dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
+		if (dmax < -180)
+			dmax += 360;
+		else if (dmax > 180)
+			dmax -= 360;
+		if (fabs(dmin) < fabs(dmax))
+			self->move_angles[YAW] = self->pos1[YAW];
+		else
+			self->move_angles[YAW] = self->pos2[YAW];
+	}
+
+	VectorSubtract (self->move_angles, current_angles, delta);
+	if (delta[0] < -180)
+		delta[0] += 360;
+	else if (delta[0] > 180)
+		delta[0] -= 360;
+	if (delta[1] < -180)
+		delta[1] += 360;
+	else if (delta[1] > 180)
+		delta[1] -= 360;
+	delta[2] = 0;
+
+	if (delta[0] > self->speed * FRAMETIME)
+		delta[0] = self->speed * FRAMETIME;
+	if (delta[0] < -1 * self->speed * FRAMETIME)
+		delta[0] = -1 * self->speed * FRAMETIME;
+	if (delta[1] > self->speed * FRAMETIME)
+		delta[1] = self->speed * FRAMETIME;
+	if (delta[1] < -1 * self->speed * FRAMETIME)
+		delta[1] = -1 * self->speed * FRAMETIME;
+
+	VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
+
+	self->nextthink = level.time + FRAMETIME;
+
+	for (ent = self->teammaster; ent; ent = ent->teamchain)
+		ent->avelocity[1] = self->avelocity[1];
+
+	// if we have adriver, adjust his velocities
+	if (self->owner)
+	{
+		float	angle;
+		float	target_z;
+		float	diff;
+		vec3_t	target;
+		vec3_t	dir;
+
+		// angular is easy, just copy ours
+		self->owner->avelocity[0] = self->avelocity[0];
+		self->owner->avelocity[1] = self->avelocity[1];
+
+		// x & y
+		angle = self->s.angles[1] + self->owner->move_origin[1];
+		angle *= (M_PI*2 / 360);
+		target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
+		target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
+		target[2] = self->owner->s.origin[2];
+
+		VectorSubtract (target, self->owner->s.origin, dir);
+		self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
+		self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
+
+		// z
+		angle = self->s.angles[PITCH] * (M_PI*2 / 360);
+		target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
+
+		diff = target_z - self->owner->s.origin[2];
+		self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
+
+		if (self->spawnflags & 65536)
+		{
+			turret_breach_fire (self);
+			self->spawnflags &= ~65536;
+		}
+	}
+}
+
+void turret_breach_finish_init (edict_t *self)
+{
+	// get and save info for muzzle location
+	if (!self->target)
+	{
+		gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
+	}
+	else
+	{
+		self->target_ent = G_PickTarget (self->target);
+		VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
+		G_FreeEdict(self->target_ent);
+	}
+
+	self->teammaster->dmg = self->dmg;
+	self->think = turret_breach_think;
+	self->think (self);
+}
+
+void SP_turret_breach (edict_t *self)
+{
+	self->solid = SOLID_BSP;
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+
+	if (!self->speed)
+		self->speed = 50;
+	if (!self->dmg)
+		self->dmg = 10;
+
+	if (!st.minpitch)
+		st.minpitch = -30;
+	if (!st.maxpitch)
+		st.maxpitch = 30;
+	if (!st.maxyaw)
+		st.maxyaw = 360;
+
+	self->pos1[PITCH] = -1 * st.minpitch;
+	self->pos1[YAW]   = st.minyaw;
+	self->pos2[PITCH] = -1 * st.maxpitch;
+	self->pos2[YAW]   = st.maxyaw;
+
+	self->ideal_yaw = self->s.angles[YAW];
+	self->move_angles[YAW] = self->ideal_yaw;
+
+	self->blocked = turret_blocked;
+
+	self->think = turret_breach_finish_init;
+	self->nextthink = level.time + FRAMETIME;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED turret_base (0 0 0) ?
+This portion of the turret changes yaw only.
+MUST be teamed with a turret_breach.
+*/
+
+void SP_turret_base (edict_t *self)
+{
+	self->solid = SOLID_BSP;
+	self->movetype = MOVETYPE_PUSH;
+	gi.setmodel (self, self->model);
+	self->blocked = turret_blocked;
+	gi.linkentity (self);
+}
+
+
+/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
+Must NOT be on the team with the rest of the turret parts.
+Instead it must target the turret_breach.
+*/
+
+void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t);
+void infantry_stand (edict_t *self);
+void monster_use (edict_t *self, edict_t *other, edict_t *activator);
+
+void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	edict_t	*ent;
+
+	// level the gun
+	self->target_ent->move_angles[0] = 0;
+
+	// remove the driver from the end of them team chain
+	for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
+		;
+	ent->teamchain = NULL;
+	self->teammaster = NULL;
+	self->flags &= ~FL_TEAMSLAVE;
+
+	self->target_ent->owner = NULL;
+	self->target_ent->teammaster->owner = NULL;
+
+	infantry_die (self, inflictor, attacker, damage, 0.0);
+}
+
+qboolean FindTarget (edict_t *self);
+
+void turret_driver_think (edict_t *self)
+{
+	vec3_t	target;
+	vec3_t	dir;
+	float	reaction_time;
+
+	self->nextthink = level.time + FRAMETIME;
+
+	if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
+		self->enemy = NULL;
+
+	if (!self->enemy)
+	{
+		if (!FindTarget (self))
+			return;
+		self->monsterinfo.trail_time = level.time;
+		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+	}
+	else
+	{
+		if (visible (self, self->enemy))
+		{
+			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
+			{
+				self->monsterinfo.trail_time = level.time;
+				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
+			}
+		}
+		else
+		{
+			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
+			return;
+		}
+	}
+
+	// let the turret know where we want it to aim
+	VectorCopy (self->enemy->s.origin, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, self->target_ent->s.origin, dir);
+	vectoangles (dir, self->target_ent->move_angles);
+
+	// decide if we should shoot
+	if (level.time < self->monsterinfo.attack_finished)
+		return;
+
+	reaction_time = (3 - skill->value) * 1.0;
+	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
+		return;
+
+	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
+	//FIXME how do we really want to pass this along?
+	self->target_ent->spawnflags |= 65536;
+}
+
+void turret_driver_link (edict_t *self)
+{
+	vec3_t	vec;
+	edict_t	*ent;
+
+	self->think = turret_driver_think;
+	self->nextthink = level.time + FRAMETIME;
+
+	self->target_ent = G_PickTarget (self->target);
+	self->target_ent->owner = self;
+	self->target_ent->teammaster->owner = self;
+	VectorCopy (self->target_ent->s.angles, self->s.angles);
+
+	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
+	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
+	vec[2] = 0;
+	self->move_origin[0] = VectorLength(vec);
+
+	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
+	vectoangles (vec, vec);
+	AnglesNormalize(vec);
+	self->move_origin[1] = vec[1];
+
+	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
+
+	// add the driver to the end of them team chain
+	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
+		;
+	ent->teamchain = self;
+	self->teammaster = self->target_ent->teammaster;
+	self->flags |= FL_TEAMSLAVE;
+}
+
+void SP_turret_driver (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_PUSH;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = 0;
+	self->mass = 200;
+	self->viewheight = 24;
+
+	self->die = turret_driver_die;
+	self->monsterinfo.stand = infantry_stand;
+
+	self->flags |= FL_NO_KNOCKBACK;
+
+	level.total_monsters++;
+
+	self->svflags |= SVF_MONSTER;
+	self->s.renderfx |= RF_FRAMELERP;
+	self->takedamage = DAMAGE_AIM;
+	self->use = monster_use;
+	self->clipmask = MASK_MONSTERSOLID;
+	VectorCopy (self->s.origin, self->s.old_origin);
+	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
+
+	if (st.item)
+	{
+		self->item = FindItemByClassname (st.item);
+		if (!self->item)
+			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
+	}
+
+	self->think = turret_driver_link;
+	self->nextthink = level.time + FRAMETIME;
+
+	gi.linkentity (self);
+}
--- /dev/null
+++ b/xatrix/g_utils.c
@@ -1,0 +1,551 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1];
+	result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1];
+	result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2];
+}
+
+
+/*
+=============
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+edict_t *G_Find (edict_t *from, int fieldofs, char *match)
+{
+	char	*s;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+
+	for ( ; from < &g_edicts[globals.num_edicts] ; from++)
+	{
+		if (!from->inuse)
+			continue;
+		s = *(char **) ((byte *)from + fieldofs);
+		if (!s)
+			continue;
+		if (!cistrcmp (s, match))
+			return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=================
+findradius
+
+Returns entities that have origins within a spherical area
+
+findradius (origin, radius)
+=================
+*/
+edict_t *findradius (edict_t *from, vec3_t org, float rad)
+{
+	vec3_t	eorg;
+	int		j;
+
+	if (!from)
+		from = g_edicts;
+	else
+		from++;
+	for ( ; from < &g_edicts[globals.num_edicts]; from++)
+	{
+		if (!from->inuse)
+			continue;
+		if (from->solid == SOLID_NOT)
+			continue;
+		for (j=0 ; j<3 ; j++)
+			eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
+		if (VectorLength(eorg) > rad)
+			continue;
+		return from;
+	}
+
+	return NULL;
+}
+
+
+/*
+=============
+G_PickTarget
+
+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 edict after from, or the beginning if NULL
+NULL will be returned if the end of the list is reached.
+
+=============
+*/
+#define MAXCHOICES	8
+
+edict_t *G_PickTarget (char *targetname)
+{
+	edict_t	*ent = NULL;
+	int		num_choices = 0;
+	edict_t	*choice[MAXCHOICES];
+
+	if (!targetname)
+	{
+		gi.dprintf("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)
+	{
+		gi.dprintf("G_PickTarget: target %s not found\n", targetname);
+		return NULL;
+	}
+
+	return choice[rand() % num_choices];
+}
+
+
+
+void Think_Delay (edict_t *ent)
+{
+	G_UseTargets (ent, ent->activator);
+	G_FreeEdict (ent);
+}
+
+/*
+==============================
+G_UseTargets
+
+the global "activator" should be set to the entity that initiated the firing.
+
+If self.delay is set, a DelayedUse entity will be created that will actually
+do the SUB_UseTargets after that many seconds have passed.
+
+Centerprints any self.message to the activator.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void G_UseTargets (edict_t *ent, edict_t *activator)
+{
+	edict_t		*t;
+
+//
+// check for a delay
+//
+	if (ent->delay)
+	{
+	// create a temp object to fire at a later time
+		t = G_Spawn();
+		t->classname = "DelayedUse";
+		t->nextthink = level.time + ent->delay;
+		t->think = Think_Delay;
+		t->activator = activator;
+		if (!activator)
+			gi.dprintf ("Think_Delay with no activator\n");
+		t->message = ent->message;
+		t->target = ent->target;
+		t->killtarget = ent->killtarget;
+		return;
+	}
+	
+	
+//
+// print the message
+//
+	if ((ent->message) && !(activator->svflags & SVF_MONSTER))
+	{
+		gi.centerprintf (activator, "%s", ent->message);
+		if (ent->noise_index)
+			gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
+		else
+			gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0);
+	}
+
+//
+// kill killtargets
+//
+	if (ent->killtarget)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->killtarget)))
+		{
+			G_FreeEdict (t);
+			if (!ent->inuse)
+			{
+				gi.dprintf("entity was removed while using killtargets\n");
+				return;
+			}
+		}
+	}
+
+//
+// fire targets
+//
+	if (ent->target)
+	{
+		t = NULL;
+		while ((t = G_Find (t, FOFS(targetname), ent->target)))
+		{
+			// doors fire area portals in a specific way
+			if (!cistrcmp(t->classname, "func_areaportal") &&
+				(!cistrcmp(ent->classname, "func_door") || !cistrcmp(ent->classname, "func_door_rotating")))
+				continue;
+
+			if (t == ent)
+			{
+				gi.dprintf ("WARNING: Entity used itself.\n");
+			}
+			else
+			{
+				if (t->use)
+					t->use (t, ent, activator);
+			}
+			if (!ent->inuse)
+			{
+				gi.dprintf("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 (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;
+}
+
+
+vec3_t VEC_UP		= {0, -1, 0};
+vec3_t MOVEDIR_UP	= {0, 0, 1};
+vec3_t VEC_DOWN		= {0, -2, 0};
+vec3_t MOVEDIR_DOWN	= {0, 0, -1};
+
+void G_SetMovedir (vec3_t angles, vec3_t movedir)
+{
+	if (VectorCompare (angles, VEC_UP))
+	{
+		VectorCopy (MOVEDIR_UP, movedir);
+	}
+	else if (VectorCompare (angles, VEC_DOWN))
+	{
+		VectorCopy (MOVEDIR_DOWN, movedir);
+	}
+	else
+	{
+		AngleVectors (angles, movedir, NULL, NULL);
+	}
+
+	VectorClear (angles);
+}
+
+
+float vectoyaw (vec3_t vec)
+{
+	float	yaw;
+	
+	if (/*vec[YAW] == 0 &&*/ vec[PITCH] == 0)
+	{
+		yaw = 0;
+		if (vec[YAW] > 0)
+			yaw = 90;
+		else if (vec[YAW] < 0)
+			yaw = -90;
+	}
+	else
+	{
+		yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI);
+		if (yaw < 0)
+			yaw += 360;
+	}
+
+	return yaw;
+}
+
+
+void vectoangles (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 = (int) (atan2(value1[1], value1[0]) * 180 / M_PI);
+		else if (value1[1] > 0)
+			yaw = 90;
+		else
+			yaw = -90;
+		if (yaw < 0)
+			yaw += 360;
+
+		forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]);
+		pitch = (int) (atan2(value1[2], forward) * 180 / M_PI);
+		if (pitch < 0)
+			pitch += 360;
+	}
+
+	angles[PITCH] = -pitch;
+	angles[YAW] = yaw;
+	angles[ROLL] = 0;
+}
+
+char *G_CopyString (char *in)
+{
+	char	*out;
+	
+	out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL);
+	strcpy (out, in);
+	return out;
+}
+
+
+void G_InitEdict (edict_t *e)
+{
+	e->inuse = true;
+	e->classname = "noclass";
+	e->gravity = 1.0;
+	e->s.number = e - g_edicts;
+}
+
+/*
+=================
+G_Spawn
+
+Either finds a free edict, or allocates a new one.
+Try to avoid reusing an entity that was recently freed, because it
+can cause the client to think the entity morphed into something else
+instead of being removed and recreated, which can cause interpolated
+angles and bad trails.
+=================
+*/
+edict_t *G_Spawn (void)
+{
+	int			i;
+	edict_t		*e;
+
+	e = &g_edicts[(int)maxclients->value+1];
+	for ( i=maxclients->value+1 ; i<globals.num_edicts ; i++, e++)
+	{
+		// the first couple seconds of server time can involve a lot of
+		// freeing and allocating, so relax the replacement policy
+		if (!e->inuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) )
+		{
+			G_InitEdict (e);
+			return e;
+		}
+	}
+	
+	if (i == game.maxentities)
+		gi.error ("ED_Alloc: no free edicts");
+		
+	globals.num_edicts++;
+	G_InitEdict (e);
+	return e;
+}
+
+/*
+=================
+G_FreeEdict
+
+Marks the edict as free
+=================
+*/
+void G_FreeEdict (edict_t *ed)
+{
+	gi.unlinkentity (ed);		// unlink from world
+
+	if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE))
+	{
+//		gi.dprintf("tried to free special edict\n");
+		return;
+	}
+
+	memset (ed, 0, sizeof(*ed));
+	ed->classname = "freed";
+	ed->freetime = level.time;
+	ed->inuse = false;
+}
+
+
+/*
+============
+G_TouchTriggers
+
+============
+*/
+void	G_TouchTriggers (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	// dead things don't activate triggers!
+	if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
+		return;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_TRIGGERS);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (!hit->touch)
+			continue;
+		hit->touch (hit, ent, NULL, NULL);
+	}
+}
+
+/*
+============
+G_TouchSolids
+
+Call after linking a new trigger in during gameplay
+to force all entities it covers to immediately touch it
+============
+*/
+void	G_TouchSolids (edict_t *ent)
+{
+	int			i, num;
+	edict_t		*touch[MAX_EDICTS], *hit;
+
+	num = gi.BoxEdicts (ent->absmin, ent->absmax, touch
+		, MAX_EDICTS, AREA_SOLID);
+
+	// be careful, it is possible to have an entity in this
+	// list removed before we get to it (killtriggered)
+	for (i=0 ; i<num ; i++)
+	{
+		hit = touch[i];
+		if (!hit->inuse)
+			continue;
+		if (ent->touch)
+			ent->touch (hit, ent, NULL, NULL);
+		if (!ent->inuse)
+			break;
+	}
+}
+
+
+
+
+/*
+==============================================================================
+
+Kill box
+
+==============================================================================
+*/
+
+/*
+=================
+KillBox
+
+Kills all entities that would touch the proposed new positioning
+of ent.  Ent should be unlinked before calling this!
+=================
+*/
+qboolean KillBox (edict_t *ent)
+{
+	trace_t		tr;
+
+	while (1)
+	{
+		tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID);
+		if (!tr.ent)
+			break;
+
+		// nail it
+		T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
+
+		// if we didn't kill it, fail
+		if (tr.ent->solid)
+			return false;
+	}
+
+	return true;		// all clear
+}
--- /dev/null
+++ b/xatrix/g_weapon.c
@@ -1,0 +1,1493 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+/*
+=================
+check_dodge
+
+This is a support routine used when a client is firing
+a non-instant attack weapon.  It checks to see if a
+monster's dodge function should be called.
+=================
+*/
+static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed)
+{
+	vec3_t	end;
+	vec3_t	v;
+	trace_t	tr;
+	float	eta;
+
+	// easy mode only ducks one quarter the time
+	if (skill->value == 0)
+	{
+		if (qrandom() > 0.25)
+			return;
+	}
+	VectorMA (start, 8192, dir, end);
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self))
+	{
+		VectorSubtract (tr.endpos, start, v);
+		eta = (VectorLength(v) - tr.ent->maxs[0]) / speed;
+		tr.ent->monsterinfo.dodge (tr.ent, self, eta);
+	}
+}
+
+
+/*
+=================
+fire_hit
+
+Used for all impact (hit/punch/slash) attacks
+=================
+*/
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick)
+{
+	trace_t		tr;
+	vec3_t		forward, right, up;
+	vec3_t		v;
+	vec3_t		point;
+	float		range;
+	vec3_t		dir;
+
+	//see if enemy is in range
+	VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+	range = VectorLength(dir);
+	if (range > aim[0])
+		return false;
+
+	if (aim[1] > self->mins[0] && aim[1] < self->maxs[0])
+	{
+		// the hit is straight on so back the range up to the edge of their bbox
+		range -= self->enemy->maxs[0];
+	}
+	else
+	{
+		// this is a side hit so adjust the "right" value out to the edge of their bbox
+		if (aim[1] < 0)
+			aim[1] = self->enemy->mins[0];
+		else
+			aim[1] = self->enemy->maxs[0];
+	}
+
+	VectorMA (self->s.origin, range, dir, point);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT);
+	if (tr.fraction < 1)
+	{
+		if (!tr.ent->takedamage)
+			return false;
+		// if it will hit any client/monster then hit the one we wanted to hit
+		if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
+			tr.ent = self->enemy;
+	}
+
+	AngleVectors(self->s.angles, forward, right, up);
+	VectorMA (self->s.origin, range, forward, point);
+	VectorMA (point, aim[1], right, point);
+	VectorMA (point, aim[2], up, point);
+	VectorSubtract (point, self->enemy->s.origin, dir);
+
+	// do the damage
+	T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT);
+
+	if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+		return false;
+
+	// do our special form of knockback here
+	VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v);
+	VectorSubtract (v, point, v);
+	VectorNormalize (v);
+	VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity);
+	if (self->enemy->velocity[2] > 0)
+		self->enemy->groundentity = NULL;
+	return true;
+}
+
+
+/*
+=================
+fire_lead
+
+This is an internal support routine used for bullet/pellet based weapons.
+=================
+*/
+static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod)
+{
+	trace_t		tr;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	float		r;
+	float		u;
+	vec3_t		water_start;
+	qboolean	water = false;
+	int			content_mask = MASK_SHOT | MASK_WATER;
+
+	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+	if (!(tr.fraction < 1.0))
+	{
+		vectoangles (aimdir, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*hspread;
+		u = crandom()*vspread;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		if (gi.pointcontents (start) & MASK_WATER)
+		{
+			water = true;
+			VectorCopy (start, water_start);
+			content_mask &= ~MASK_WATER;
+		}
+
+		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
+
+		// see if we hit water
+		if (tr.contents & MASK_WATER)
+		{
+			int		color;
+
+			water = true;
+			VectorCopy (tr.endpos, water_start);
+
+			if (!VectorCompare (start, tr.endpos))
+			{
+				if (tr.contents & CONTENTS_WATER)
+				{
+					if (strcmp(tr.surface->name, "*brwater") == 0)
+						color = SPLASH_BROWN_WATER;
+					else
+						color = SPLASH_BLUE_WATER;
+				}
+				else if (tr.contents & CONTENTS_SLIME)
+					color = SPLASH_SLIME;
+				else if (tr.contents & CONTENTS_LAVA)
+					color = SPLASH_LAVA;
+				else
+					color = SPLASH_UNKNOWN;
+
+				if (color != SPLASH_UNKNOWN)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (TE_SPLASH);
+					gi.WriteByte (8);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.WriteByte (color);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+				}
+
+				// change bullet's course when it enters water
+				VectorSubtract (end, start, dir);
+				vectoangles (dir, dir);
+				AngleVectors (dir, forward, right, up);
+				r = crandom()*hspread*2;
+				u = crandom()*vspread*2;
+				VectorMA (water_start, 8192, forward, end);
+				VectorMA (end, r, right, end);
+				VectorMA (end, u, up, end);
+			}
+
+			// re-trace ignoring water this time
+			tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
+		}
+	}
+
+	// send gun puff / flash
+	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+	{
+		if (tr.fraction < 1.0)
+		{
+			if (tr.ent->takedamage)
+			{
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod);
+			}
+			else
+			{
+				if (strncmp (tr.surface->name, "sky", 3) != 0)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (te_impact);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+
+					if (self->client)
+						PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+				}
+			}
+		}
+	}
+
+	// if went through water, determine where the end and make a bubble trail
+	if (water)
+	{
+		vec3_t	pos;
+
+		VectorSubtract (tr.endpos, water_start, dir);
+		VectorNormalize (dir);
+		VectorMA (tr.endpos, -2, dir, pos);
+		if (gi.pointcontents (pos) & MASK_WATER)
+			VectorCopy (pos, tr.endpos);
+		else
+			tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+
+		VectorAdd (water_start, tr.endpos, pos);
+		VectorScale (pos, 0.5, pos);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BUBBLETRAIL);
+		gi.WritePosition (water_start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (pos, MULTICAST_PVS);
+	}
+}
+
+
+/*
+=================
+fire_bullet
+
+Fires a single round.  Used for machinegun and chaingun.  Would be fine for
+pistols, rifles, etc....
+=================
+*/
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
+{
+	fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_shotgun
+
+Shoots shotgun pellets.  Used by shotgun and super shotgun.
+=================
+*/
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod)
+{
+	int		i;
+
+	for (i = 0; i < count; i++)
+		fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
+}
+
+
+/*
+=================
+fire_blaster
+
+Fires a single blaster bolt.  Used by the blaster and hyper blaster.
+=================
+*/
+void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	int		mod;
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		if (self->spawnflags & 1)
+			mod = MOD_HYPERBLASTER;
+		else
+			mod = MOD_BLASTER;
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod);
+	}
+	else
+	{
+		gi.WriteByte (svc_temp_entity);
+		// RAFAEL
+		//if (self->s.effects & TE_BLUEHYPERBLASTER)
+		//	gi.WriteByte (TE_BLUEHYPERBLASTER);
+		//else
+			gi.WriteByte (TE_BLASTER);
+		gi.WritePosition (self->s.origin);
+		if (!plane)
+			gi.WriteDir (vec3_origin);
+		else
+			gi.WriteDir (plane->normal);
+		gi.multicast (self->s.origin, MULTICAST_PVS);
+	}
+
+	G_FreeEdict (self);
+}
+
+void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper)
+{
+	edict_t	*bolt;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn();
+	bolt->svflags = SVF_DEADMONSTER;
+	// yes, I know it looks weird that projectiles are deadmonsters
+	// what this means is that when prediction is used against the object
+	// (blaster/hyperblaster shots), the player won't be solid clipped against
+	// the object.  Right now trying to run into a firing hyperblaster
+	// is very jerky since you are predicted 'against' the shots.
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->s.effects |= effect;
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+	bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2");
+	bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
+	bolt->owner = self;
+	bolt->touch = blaster_touch;
+	bolt->nextthink = level.time + 2;
+	bolt->think = G_FreeEdict;
+	bolt->dmg = damage;
+	bolt->classname = "bolt";
+	if (hyper)
+		bolt->spawnflags = 1;
+	gi.linkentity (bolt);
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+}	
+
+// RAFAEL
+void fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
+{
+	edict_t *bolt;
+	trace_t tr;
+
+	VectorNormalize (dir);
+
+	bolt = G_Spawn ();
+	VectorCopy (start, bolt->s.origin);
+	VectorCopy (start, bolt->s.old_origin);
+	vectoangles (dir, bolt->s.angles);
+	VectorScale (dir, speed, bolt->velocity);
+	bolt->movetype = MOVETYPE_FLYMISSILE;
+	bolt->clipmask = MASK_SHOT;
+	bolt->solid = SOLID_BBOX;
+	bolt->s.effects |= effect;
+	VectorClear (bolt->mins);
+	VectorClear (bolt->maxs);
+
+	bolt->s.modelindex = gi.modelindex ("models/objects/blaser/tris.md2");
+	bolt->s.sound = gi.soundindex ("misc/lasfly.wav");
+	bolt->owner = self;
+	bolt->touch = blaster_touch;
+	bolt->nextthink = level.time + 2;
+	bolt->think = G_FreeEdict;
+	bolt->dmg = damage;
+	bolt->classname = "bolt";
+	gi.linkentity (bolt);
+
+	if (self->client)
+		check_dodge (self, bolt->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT);
+
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (bolt->s.origin, -10, dir, bolt->s.origin);
+		bolt->touch (bolt, tr.ent, NULL, NULL);
+	}
+
+}
+
+/*
+=================
+fire_grenade
+=================
+*/
+static void Grenade_Explode (edict_t *ent)
+{
+	vec3_t		origin;
+	int			mod;
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	//FIXME: if we are onground then raise our Z just a bit since we are a point?
+	if (ent->enemy)
+	{
+		float	points;
+		vec3_t	v;
+		vec3_t	dir;
+
+		VectorAdd (ent->enemy->mins, ent->enemy->maxs, v);
+		VectorMA (ent->enemy->s.origin, 0.5, v, v);
+		VectorSubtract (ent->s.origin, v, v);
+		points = ent->dmg - 0.5 * VectorLength (v);
+		VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir);
+		if (ent->spawnflags & 1)
+			mod = MOD_HANDGRENADE;
+		else
+			mod = MOD_GRENADE;
+		T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
+	}
+
+	if (ent->spawnflags & 2)
+		mod = MOD_HELD_GRENADE;
+	else if (ent->spawnflags & 1)
+		mod = MOD_HG_SPLASH;
+	else
+		mod = MOD_G_SPLASH;
+	T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod);
+
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION_WATER);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	}
+	else
+	{
+		if (ent->groundentity)
+			gi.WriteByte (TE_GRENADE_EXPLOSION);
+		else
+			gi.WriteByte (TE_ROCKET_EXPLOSION);
+	}
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *, csurface_t *surf)
+{
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (!other->takedamage)
+	{
+		if (ent->spawnflags & 1)
+		{
+			if (qrandom() > 0.5)
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0);
+			else
+				gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0);
+		}
+		else
+		{
+			gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0);
+		}
+		return;
+	}
+
+	ent->enemy = other;
+	Grenade_Explode (ent);
+}
+
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "grenade";
+
+	gi.linkentity (grenade);
+}
+
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
+{
+	edict_t	*grenade;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	grenade = G_Spawn();
+	VectorCopy (start, grenade->s.origin);
+	VectorScale (aimdir, speed, grenade->velocity);
+	VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity);
+	VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity);
+	VectorSet (grenade->avelocity, 300, 300, 300);
+	grenade->movetype = MOVETYPE_BOUNCE;
+	grenade->clipmask = MASK_SHOT;
+	grenade->solid = SOLID_BBOX;
+	grenade->s.effects |= EF_GRENADE;
+	VectorClear (grenade->mins);
+	VectorClear (grenade->maxs);
+	grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2");
+	grenade->owner = self;
+	grenade->touch = Grenade_Touch;
+	grenade->nextthink = level.time + timer;
+	grenade->think = Grenade_Explode;
+	grenade->dmg = damage;
+	grenade->dmg_radius = damage_radius;
+	grenade->classname = "hgrenade";
+	if (held)
+		grenade->spawnflags = 3;
+	else
+		grenade->spawnflags = 1;
+	grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav");
+
+	if (timer <= 0.0)
+		Grenade_Explode (grenade);
+	else
+	{
+		gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0);
+		gi.linkentity (grenade);
+	}
+}
+
+
+/*
+=================
+fire_rocket
+=================
+*/
+void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	vec3_t		origin;
+	int			n;
+
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	// calculate position for the explosion entity
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+	if (other->takedamage)
+	{
+		T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET);
+	}
+	else
+	{
+		// don't throw any debris in net games
+		if (!deathmatch->value && !coop->value)
+		{
+			if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING)))
+			{
+				n = rand() % 5;
+				while(n--)
+					ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin);
+			}
+		}
+	}
+
+	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
+
+	gi.WriteByte (svc_temp_entity);
+	if (ent->waterlevel)
+		gi.WriteByte (TE_ROCKET_EXPLOSION_WATER);
+	else
+		gi.WriteByte (TE_ROCKET_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PHS);
+
+	G_FreeEdict (ent);
+}
+
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
+{
+	edict_t	*rocket;
+
+	rocket = G_Spawn();
+	VectorCopy (start, rocket->s.origin);
+	VectorCopy (dir, rocket->movedir);
+	vectoangles (dir, rocket->s.angles);
+	VectorScale (dir, speed, rocket->velocity);
+	rocket->movetype = MOVETYPE_FLYMISSILE;
+	rocket->clipmask = MASK_SHOT;
+	rocket->solid = SOLID_BBOX;
+	rocket->s.effects |= EF_ROCKET;
+	VectorClear (rocket->mins);
+	VectorClear (rocket->maxs);
+	rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
+	rocket->owner = self;
+	rocket->touch = rocket_touch;
+	rocket->nextthink = level.time + 8000/speed;
+	rocket->think = G_FreeEdict;
+	rocket->dmg = damage;
+	rocket->radius_dmg = radius_damage;
+	rocket->dmg_radius = damage_radius;
+	rocket->s.sound = gi.soundindex ("weapons/rockfly.wav");
+	rocket->classname = "rocket";
+
+	if (self->client)
+		check_dodge (self, rocket->s.origin, dir, speed);
+
+	gi.linkentity (rocket);
+}
+
+
+/*
+=================
+fire_rail
+=================
+*/
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
+{
+	vec3_t		from;
+	vec3_t		end;
+	trace_t		tr;
+	edict_t		*ignore;
+	int			mask;
+	qboolean	water;
+
+	VectorMA (start, 8192, aimdir, end);
+	VectorCopy (start, from);
+	ignore = self;
+	water = false;
+	mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
+	while (ignore)
+	{
+		tr = gi.trace (from, NULL, NULL, end, ignore, mask);
+
+		if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA))
+		{
+			mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA);
+			water = true;
+		}
+		else
+		{
+			//ZOID--added so rail goes through SOLID_BBOX entities (gibs, etc)
+			if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client) ||
+				(tr.ent->solid == SOLID_BBOX))
+				ignore = tr.ent;
+			else
+				ignore = NULL;
+
+			if ((tr.ent != self) && (tr.ent->takedamage))
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
+		}
+
+		VectorCopy (tr.endpos, from);
+	}
+
+	// send gun puff / flash
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_RAILTRAIL);
+	gi.WritePosition (start);
+	gi.WritePosition (tr.endpos);
+	gi.multicast (self->s.origin, MULTICAST_PHS);
+//	gi.multicast (start, MULTICAST_PHS);
+	if (water)
+	{
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_RAILTRAIL);
+		gi.WritePosition (start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (tr.endpos, MULTICAST_PHS);
+	}
+
+	if (self->client)
+		PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+}
+
+
+/*
+=================
+fire_bfg
+=================
+*/
+void bfg_explode (edict_t *self)
+{
+	edict_t	*ent;
+	float	points;
+	vec3_t	v;
+	float	dist;
+
+	if (self->s.frame == 0)
+	{
+		// the BFG effect
+		ent = NULL;
+		while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL)
+		{
+			if (!ent->takedamage)
+				continue;
+			if (ent == self->owner)
+				continue;
+			if (!CanDamage (ent, self))
+				continue;
+			if (!CanDamage (ent, self->owner))
+				continue;
+
+			VectorAdd (ent->mins, ent->maxs, v);
+			VectorMA (ent->s.origin, 0.5, v, v);
+			VectorSubtract (self->s.origin, v, v);
+			dist = VectorLength(v);
+			points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius));
+			if (ent == self->owner)
+				points = points * 0.5;
+
+			gi.WriteByte (svc_temp_entity);
+			gi.WriteByte (TE_BFG_EXPLOSION);
+			gi.WritePosition (ent->s.origin);
+			gi.multicast (ent->s.origin, MULTICAST_PHS);
+			T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT);
+		}
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+	self->s.frame++;
+	if (self->s.frame == 5)
+		self->think = G_FreeEdict;
+}
+
+void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	// core explosion - prevents firing it into the wall/floor
+	if (other->takedamage)
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST);
+	T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST);
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0);
+	self->solid = SOLID_NOT;
+	self->touch = NULL;
+	VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin);
+	VectorClear (self->velocity);
+	self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2");
+	self->s.frame = 0;
+	self->s.sound = 0;
+	self->s.effects &= ~EF_ANIM_ALLFAST;
+	self->think = bfg_explode;
+	self->nextthink = level.time + FRAMETIME;
+	self->enemy = other;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BFG_BIGEXPLOSION);
+	gi.WritePosition (self->s.origin);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+
+void bfg_think (edict_t *self)
+{
+	edict_t	*ent;
+	edict_t	*ignore;
+	vec3_t	point;
+	vec3_t	dir;
+	vec3_t	start;
+	vec3_t	end;
+	int		dmg;
+	trace_t	tr;
+
+	if (deathmatch->value)
+		dmg = 5;
+	else
+		dmg = 10;
+
+	ent = NULL;
+	while ((ent = findradius(ent, self->s.origin, 256)) != NULL)
+	{
+		if (ent == self)
+			continue;
+
+		if (ent == self->owner)
+			continue;
+
+		if (!ent->takedamage)
+			continue;
+
+		if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0))
+			continue;
+
+		VectorMA (ent->absmin, 0.5, ent->size, point);
+
+		VectorSubtract (point, self->s.origin, dir);
+		VectorNormalize (dir);
+
+		ignore = self;
+		VectorCopy (self->s.origin, start);
+		VectorMA (start, 2048, dir, end);
+		while(1)
+		{
+			tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER);
+
+			if (!tr.ent)
+				break;
+
+			// hurt it if we can
+			if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner))
+				T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER);
+
+			// if we hit something that's not a monster or player we're done
+			if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
+			{
+				gi.WriteByte (svc_temp_entity);
+				gi.WriteByte (TE_LASER_SPARKS);
+				gi.WriteByte (4);
+				gi.WritePosition (tr.endpos);
+				gi.WriteDir (tr.plane.normal);
+				gi.WriteByte (self->s.skinnum);
+				gi.multicast (tr.endpos, MULTICAST_PVS);
+				break;
+			}
+
+			ignore = tr.ent;
+			VectorCopy (tr.endpos, start);
+		}
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BFG_LASER);
+		gi.WritePosition (self->s.origin);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (self->s.origin, MULTICAST_PHS);
+	}
+
+	self->nextthink = level.time + FRAMETIME;
+}
+
+
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
+{
+	edict_t	*bfg;
+
+	bfg = G_Spawn();
+	VectorCopy (start, bfg->s.origin);
+	VectorCopy (dir, bfg->movedir);
+	vectoangles (dir, bfg->s.angles);
+	VectorScale (dir, speed, bfg->velocity);
+	bfg->movetype = MOVETYPE_FLYMISSILE;
+	bfg->clipmask = MASK_SHOT;
+	bfg->solid = SOLID_BBOX;
+	bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
+	VectorClear (bfg->mins);
+	VectorClear (bfg->maxs);
+	bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2");
+	bfg->owner = self;
+	bfg->touch = bfg_touch;
+	bfg->nextthink = level.time + 8000/speed;
+	bfg->think = G_FreeEdict;
+	bfg->radius_dmg = damage;
+	bfg->dmg_radius = damage_radius;
+	bfg->classname = "bfg blast";
+	bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav");
+
+	bfg->think = bfg_think;
+	bfg->nextthink = level.time + FRAMETIME;
+	bfg->teammaster = bfg;
+	bfg->teamchain = NULL;
+
+	if (self->client)
+		check_dodge (self, bfg->s.origin, dir, speed);
+
+	gi.linkentity (bfg);
+}
+
+
+// RAFAEL
+
+/*
+	fire_ionripper
+*/
+
+void ionripper_sparks (edict_t *self)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WELDING_SPARKS);
+	gi.WriteByte (0);
+	gi.WritePosition (self->s.origin);
+	gi.WriteDir (vec3_origin);
+	gi.WriteByte (0xe4 + (rand()&3));
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	G_FreeEdict (self);
+}
+
+// RAFAEL
+void ionripper_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+			PlayerNoise (self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+	{
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_RIPPER);
+
+	}
+	else
+	{
+		return;
+	}
+
+	G_FreeEdict (self);
+}
+
+
+// RAFAEL
+void fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
+{
+	edict_t *ion;
+	trace_t tr;
+
+	VectorNormalize (dir);
+
+	ion = G_Spawn ();
+	VectorCopy (start, ion->s.origin);
+	VectorCopy (start, ion->s.old_origin);
+	vectoangles (dir, ion->s.angles);
+	VectorScale (dir, speed, ion->velocity);
+
+	ion->movetype = MOVETYPE_WALLBOUNCE;
+	ion->clipmask = MASK_SHOT;
+	ion->solid = SOLID_BBOX;
+	ion->s.effects |= effect;
+
+	ion->s.renderfx |= RF_FULLBRIGHT;
+
+	VectorClear (ion->mins);
+	VectorClear (ion->maxs);
+	ion->s.modelindex = gi.modelindex ("models/objects/boomrang/tris.md2");
+	ion->s.sound = gi.soundindex ("misc/lasfly.wav");
+	ion->owner = self;
+	ion->touch = ionripper_touch;
+	ion->nextthink = level.time + 3;
+	ion->think = ionripper_sparks;
+	ion->dmg = damage;
+	ion->dmg_radius = 100;
+	gi.linkentity (ion);
+
+	if (self->client)
+		check_dodge (self, ion->s.origin, dir, speed);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, ion->s.origin, ion, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (ion->s.origin, -10, dir, ion->s.origin);
+		ion->touch (ion, tr.ent, NULL, NULL);
+	}
+
+}
+
+
+// RAFAEL
+/*
+fire_heat
+*/
+
+
+void heat_think (edict_t *self)
+{
+	edict_t		*target = NULL;
+	edict_t		*aquire = NULL;
+	vec3_t		vec;
+	vec3_t		oldang;
+	int			len;
+	int			oldlen = 0;
+
+	VectorClear (vec);
+
+	// aquire new target
+	while (( target = findradius (target, self->s.origin, 1024)) != NULL)
+	{
+		
+		if (self->owner == target)
+			continue;
+		if (!target->svflags & SVF_MONSTER)
+			continue;
+		if (!target->client)
+			continue;
+		if (target->health <= 0)
+			continue;
+		if (!visible (self, target))
+			continue;
+		
+		// if we need to reduce the tracking cone
+		/*
+		{
+			vec3_t	vec;
+			float	dot;
+			vec3_t	forward;
+	
+			AngleVectors (self->s.angles, forward, NULL, NULL);
+			VectorSubtract (target->s.origin, self->s.origin, vec);
+			VectorNormalize (vec);
+			dot = DotProduct (vec, forward);
+	
+			if (dot > 0.6)
+				continue;
+		}
+		*/
+
+		if (!infront (self, target))
+			continue;
+
+		VectorSubtract (self->s.origin, target->s.origin, vec);
+		len = VectorLength (vec);
+
+		if (aquire == NULL || len < oldlen)
+		{
+			aquire = target;
+			self->target_ent = aquire;
+			oldlen = len;
+		}
+	}
+
+	if (aquire != NULL)
+	{
+		VectorCopy (self->s.angles, oldang);
+		VectorSubtract (aquire->s.origin, self->s.origin, vec);
+		
+		vectoangles (vec, self->s.angles);
+		
+		VectorNormalize (vec);
+		VectorCopy (vec, self->movedir);
+		VectorScale (vec, 500, self->velocity);
+	}
+
+	self->nextthink = level.time + 0.1;
+}
+
+// RAFAEL
+void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
+{
+	edict_t *heat;
+
+	heat = G_Spawn();
+	VectorCopy (start, heat->s.origin);
+	VectorCopy (dir, heat->movedir);
+	vectoangles (dir, heat->s.angles);
+	VectorScale (dir, speed, heat->velocity);
+	heat->movetype = MOVETYPE_FLYMISSILE;
+	heat->clipmask = MASK_SHOT;
+	heat->solid = SOLID_BBOX;
+	heat->s.effects |= EF_ROCKET;
+	VectorClear (heat->mins);
+	VectorClear (heat->maxs);
+	heat->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2");
+	heat->owner = self;
+	heat->touch = rocket_touch;
+
+	heat->nextthink = level.time + 0.1;
+	heat->think = heat_think;
+	
+	heat->dmg = damage;
+	heat->radius_dmg = radius_damage;
+	heat->dmg_radius = damage_radius;
+	heat->s.sound = gi.soundindex ("weapons/rockfly.wav");
+
+	if (self->client)
+		check_dodge (self, heat->s.origin, dir, speed);
+
+	gi.linkentity (heat);
+}
+
+
+
+// RAFAEL
+
+/*
+	fire_plasma
+*/
+
+void plasma_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+	vec3_t		origin;
+
+	if (other == ent->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (ent);
+		return;
+	}
+
+	if (ent->owner->client)
+		PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT);
+
+	// calculate position for the explosion entity
+	VectorMA (ent->s.origin, -0.02, ent->velocity, origin);
+
+	if (other->takedamage)
+	{
+		T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_PHALANX);
+	}
+	
+	T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_PHALANX);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PLASMA_EXPLOSION);
+	gi.WritePosition (origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	
+	G_FreeEdict (ent);
+}
+
+
+// RAFAEL
+void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
+{
+	edict_t *plasma;
+
+	plasma = G_Spawn();
+	VectorCopy (start, plasma->s.origin);
+	VectorCopy (dir, plasma->movedir);
+	vectoangles (dir, plasma->s.angles);
+	VectorScale (dir, speed, plasma->velocity);
+	plasma->movetype = MOVETYPE_FLYMISSILE;
+	plasma->clipmask = MASK_SHOT;
+	plasma->solid = SOLID_BBOX;
+
+	VectorClear (plasma->mins);
+	VectorClear (plasma->maxs);
+	
+	plasma->owner = self;
+	plasma->touch = plasma_touch;
+	plasma->nextthink = level.time + 8000/speed;
+	plasma->think = G_FreeEdict;
+	plasma->dmg = damage;
+	plasma->radius_dmg = radius_damage;
+	plasma->dmg_radius = damage_radius;
+	plasma->s.sound = gi.soundindex ("weapons/rockfly.wav");
+	
+	plasma->s.modelindex = gi.modelindex ("sprites/s_photon.sp2");
+	plasma->s.effects |= EF_PLASMA | EF_ANIM_ALLFAST;
+	
+	if (self->client)
+		check_dodge (self, plasma->s.origin, dir, speed);
+
+	gi.linkentity (plasma);
+
+	
+}
+
+// RAFAEL
+extern void SP_item_foodcube (edict_t *best);
+// RAFAEL
+static void Trap_Think (edict_t *ent)
+{
+	edict_t	*target = NULL;
+	edict_t	*best = NULL;
+	vec3_t	vec;
+	int		len, i;
+	int		oldlen = 8000;
+	vec3_t	forward, right, up;
+	
+	if (ent->timestamp < level.time)
+	{
+		BecomeExplosion1(ent);
+		// note to self
+		// cause explosion damage???
+		return;
+	}
+	
+	ent->nextthink = level.time + 0.1;
+	
+	if (!ent->groundentity)
+		return;
+
+	// ok lets do the blood effect
+	if (ent->s.frame > 4)
+	{
+		if (ent->s.frame == 5)
+		{
+			if (ent->wait == 64)
+				gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_IDLE, 0);
+
+			ent->wait -= 2;
+			ent->delay += level.time;
+
+			for (i=0; i<3; i++)
+			{
+				
+				best = G_Spawn();
+
+				if (strcmp (ent->enemy->classname, "monster_gekk") == 0)
+				{
+					best->s.modelindex = gi.modelindex ("models/objects/gekkgib/torso/tris.md2");	
+					best->s.effects |= TE_GREENBLOOD;
+				}
+				else if (ent->mass > 200)
+				{
+					best->s.modelindex = gi.modelindex ("models/objects/gibs/chest/tris.md2");	
+					best->s.effects |= TE_BLOOD;
+				}
+				else
+				{
+					best->s.modelindex = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2");	
+					best->s.effects |= TE_BLOOD;
+				}
+
+				AngleVectors (ent->s.angles, forward, right, up);
+				
+				RotatePointAroundVector( vec, up, right, ((360.0/3)* i)+ent->delay);
+				VectorMA (vec, ent->wait/2, vec, vec);
+				VectorAdd(vec, ent->s.origin, vec);
+				VectorAdd(vec, forward, best->s.origin);
+  
+				best->s.origin[2] = ent->s.origin[2] + ent->wait;
+				
+				VectorCopy (ent->s.angles, best->s.angles);
+  
+				best->solid = SOLID_NOT;
+				best->s.effects |= EF_GIB;
+				best->takedamage = DAMAGE_YES;
+		
+				best->movetype = MOVETYPE_TOSS;
+				best->svflags |= SVF_MONSTER;
+				best->deadflag = DEAD_DEAD;
+		  
+				VectorClear (best->mins);
+				VectorClear (best->maxs);
+
+				best->watertype = gi.pointcontents(best->s.origin);
+				if (best->watertype & MASK_WATER)
+					best->waterlevel = 1;
+
+				best->nextthink = level.time + 0.1;
+				best->think = G_FreeEdict;
+				gi.linkentity (best);
+			}
+				
+			if (ent->wait < 19)
+				ent->s.frame ++;
+
+			return;
+		}
+		ent->s.frame ++;
+		if (ent->s.frame == 8)
+		{
+			ent->nextthink = level.time + 1.0;
+			ent->think = G_FreeEdict;
+
+			best = G_Spawn ();
+			SP_item_foodcube (best);
+			VectorCopy (ent->s.origin, best->s.origin);
+			best->s.origin[2]+= 16;
+			best->velocity[2] = 400;
+			best->count = ent->mass;
+			gi.linkentity (best);
+			return;
+		}
+		return;
+	}
+	
+	ent->s.effects &= ~EF_TRAP;
+	if (ent->s.frame >= 4)
+	{
+		ent->s.effects |= EF_TRAP;
+		VectorClear (ent->mins);
+		VectorClear (ent->maxs);
+
+	}
+
+	if (ent->s.frame < 4)
+		ent->s.frame++;
+
+	while ((target = findradius(target, ent->s.origin, 256)) != NULL)
+	{
+		if (target == ent)
+			continue;
+		if (!(target->svflags & SVF_MONSTER) && !target->client)
+			continue;
+		// if (target == ent->owner)
+		//	continue;
+		if (target->health <= 0)
+			continue;
+		if (!visible (ent, target))
+		 	continue;
+		if (!best)
+		{
+			best = target;
+			continue;
+		}
+		VectorSubtract (ent->s.origin, target->s.origin, vec);
+		len = VectorLength (vec);
+		if (len < oldlen)
+		{
+			oldlen = len;
+			best = target;
+		}
+	}
+
+	// pull the enemy in
+	if (best)
+	{
+		vec3_t	forward;
+
+		if (best->groundentity)
+		{
+			best->s.origin[2] += 1;
+			best->groundentity = NULL;
+		}
+		VectorSubtract (ent->s.origin, best->s.origin, vec);
+		len = VectorLength (vec);
+		if (best->client)
+		{
+			VectorNormalize (vec);
+			VectorMA (best->velocity, 250, vec, best->velocity);
+		}
+		else
+		{
+			best->ideal_yaw = vectoyaw(vec);	
+			M_ChangeYaw (best);
+			AngleVectors (best->s.angles, forward, NULL, NULL);
+			VectorScale (forward, 256, best->velocity);
+		}
+
+		gi.sound(ent, CHAN_VOICE, gi.soundindex ("weapons/trapsuck.wav"), 1, ATTN_IDLE, 0);
+		
+		if (len < 32)
+		{	
+			if (best->mass < 400)
+			{
+				T_Damage (best, ent, ent->owner, vec3_origin, best->s.origin, vec3_origin, 100000, 1, 0, MOD_TRAP);
+				ent->enemy = best;
+				ent->wait = 64;
+				VectorCopy (ent->s.origin, ent->s.old_origin);
+				ent->timestamp = level.time + 30;
+				if (deathmatch->value)
+					ent->mass = best->mass/4;
+				else
+					ent->mass = best->mass/10;
+				// ok spawn the food cube
+				ent->s.frame = 5;	
+			}
+			else
+			{
+				BecomeExplosion1(ent);
+				// note to self
+				// cause explosion damage???
+				return;
+			}
+				
+		}
+	}
+
+		
+}
+
+
+// RAFAEL
+void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held)
+{
+	edict_t	*trap;
+	vec3_t	dir;
+	vec3_t	forward, right, up;
+
+	vectoangles (aimdir, dir);
+	AngleVectors (dir, forward, right, up);
+
+	trap = G_Spawn();
+	VectorCopy (start, trap->s.origin);
+	VectorScale (aimdir, speed, trap->velocity);
+	VectorMA (trap->velocity, 200 + crandom() * 10.0, up, trap->velocity);
+	VectorMA (trap->velocity, crandom() * 10.0, right, trap->velocity);
+	VectorSet (trap->avelocity, 0, 300, 0);
+	trap->movetype = MOVETYPE_BOUNCE;
+	trap->clipmask = MASK_SHOT;
+	trap->solid = SOLID_BBOX;
+//	VectorClear (trap->mins);
+//	VectorClear (trap->maxs);
+	VectorSet (trap->mins, -4, -4, 0);
+	VectorSet (trap->maxs, 4, 4, 8);
+	trap->s.modelindex = gi.modelindex ("models/weapons/z_trap/tris.md2");
+	trap->owner = self;
+	trap->nextthink = level.time + 1.0;
+	trap->think = Trap_Think;
+	trap->dmg = damage;
+	trap->dmg_radius = damage_radius;
+	trap->classname = "htrap";
+	// RAFAEL 16-APR-98
+	trap->s.sound = gi.soundindex ("weapons/traploop.wav");
+	// END 16-APR-98
+	if (held)
+		trap->spawnflags = 3;
+	else
+		trap->spawnflags = 1;
+	
+	if (timer <= 0.0)
+		Grenade_Explode (trap);
+	else
+	{
+		// gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/trapdown.wav"), 1, ATTN_NORM, 0);
+		gi.linkentity (trap);
+	}
+	
+	trap->timestamp = level.time + 30;
+
+}
--- /dev/null
+++ b/xatrix/game.h
@@ -1,0 +1,1289 @@
+
+// game.h -- game dll information visible to server
+
+#define	GAME_API_VERSION	3
+
+// edict->svflags
+
+#define	SVF_NOCLIENT			0x00000001	// don't send entity to clients, even if it has effects
+#define	SVF_DEADMONSTER			0x00000002	// treat as CONTENTS_DEADMONSTER for collision
+#define	SVF_MONSTER				0x00000004	// treat as CONTENTS_MONSTER for collision
+
+// edict->solid values
+
+typedef enum
+{
+SOLID_NOT,			// no interaction with other objects
+SOLID_TRIGGER,		// only touch when inside, after moving
+SOLID_BBOX,			// touch on edge
+SOLID_BSP			// bsp clip, touch on edge
+} solid_t;
+
+//===============================================================
+
+// link_t is only used for entity area links now
+typedef struct link_s
+{
+	struct link_s	*prev, *next;
+} link_t;
+
+#define	MAX_ENT_CLUSTERS	16
+
+
+typedef struct edict_t edict_t;	// this is in dat.h too, should be removed
+typedef struct gclient_t gclient_t;
+
+
+
+//
+// functions provided by the main engine
+//
+typedef struct
+{
+	// special messages
+	void	(*bprintf) (int printlevel, char *fmt, ...);
+	void	(*dprintf) (char *fmt, ...);
+	void	(*cprintf) (edict_t *ent, int printlevel, char *fmt, ...);
+	void	(*centerprintf) (edict_t *ent, char *fmt, ...);
+	void	(*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs);
+	void	(*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs);
+
+	// config strings hold all the index strings, the lightstyles,
+	// and misc data like the sky definition and cdtrack.
+	// All of the current configstrings are sent to clients when
+	// they connect, and changes are sent to all connected clients.
+	void	(*configstring) (int num, char *string);
+
+	void	(*error) (char *fmt, ...);
+
+	// the *index functions create configstrings and some internal server state
+	int		(*modelindex) (char *name);
+	int		(*soundindex) (char *name);
+	int		(*imageindex) (char *name);
+
+	void	(*setmodel) (edict_t *ent, char *name);
+
+	// collision detection
+	trace_t	(*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask);
+	int		(*pointcontents) (vec3_t point);
+	qboolean	(*inPVS) (vec3_t p1, vec3_t p2);
+	qboolean	(*inPHS) (vec3_t p1, vec3_t p2);
+	void		(*SetAreaPortalState) (int portalnum, qboolean open);
+	qboolean	(*AreasConnected) (int area1, int area2);
+
+	// 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.
+	void	(*linkentity) (edict_t *ent);
+	void	(*unlinkentity) (edict_t *ent);		// call before removing an interactive edict
+	int		(*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list,	int maxcount, int areatype);
+	void	(*Pmove) (pmove_t *pmove);		// player movement code common with client prediction
+
+	// network messaging
+	void	(*multicast) (vec3_t origin, multicast_t to);
+	void	(*unicast) (edict_t *ent, qboolean reliable);
+	void	(*WriteChar) (int c);
+	void	(*WriteByte) (int c);
+	void	(*WriteShort) (int c);
+	void	(*WriteLong) (int c);
+	void	(*WriteFloat) (float f);
+	void	(*WriteString) (char *s);
+	void	(*WritePosition) (vec3_t pos);	// some fractional bits
+	void	(*WriteDir) (vec3_t pos);		// single byte encoded, very coarse
+	void	(*WriteAngle) (float f);
+
+	// managed memory allocation
+	void	*(*TagMalloc) (int size, int tag);
+	void	(*TagFree) (void *block);
+	void	(*FreeTags) (int tag);
+
+	// console variable interaction
+	cvar_t	*(*cvar) (char *var_name, char *value, int flags);
+	cvar_t	*(*cvar_set) (char *var_name, char *value);
+	cvar_t	*(*cvar_forceset) (char *var_name, char *value);
+
+	// ClientCommand and ServerCommand parameter access
+	int		(*argc) (void);
+	char	*(*argv) (int n);
+	char	*(*args) (void);	// concatenation of all argv >= 1
+
+	// add commands to the server console as if they were typed in
+	// for map changing, etc
+	void	(*AddCommandString) (char *text);
+
+	void	(*DebugGraph) (float value, int color);
+} game_import_t;
+
+//
+// functions exported by the game subsystem
+//
+typedef struct
+{
+	int			apiversion;
+
+	// the init function will only be called when a game starts,
+	// not each time a level is loaded.  Persistant data for clients
+	// and the server can be allocated in init
+	void		(*Init) (void);
+	void		(*Shutdown) (void);
+
+	// each new level entered will cause a call to SpawnEntities
+	void		(*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint);
+
+	// Read/Write Game is for storing persistant cross level information
+	// about the world state and the clients.
+	// WriteGame is called every time a level is exited.
+	// ReadGame is called on a loadgame.
+	void		(*WriteGame) (char *filename, qboolean autosave);
+	void		(*ReadGame) (char *filename);
+
+	// ReadLevel is called after the default map information has been
+	// loaded with SpawnEntities
+	void		(*WriteLevel) (char *filename);
+	void		(*ReadLevel) (char *filename);
+
+	qboolean	(*ClientConnect) (edict_t *ent, char *userinfo);
+	void		(*ClientBegin) (edict_t *ent);
+	void		(*ClientUserinfoChanged) (edict_t *ent, char *userinfo);
+	void		(*ClientDisconnect) (edict_t *ent);
+	void		(*ClientCommand) (edict_t *ent);
+	void		(*ClientThink) (edict_t *ent, usercmd_t *cmd);
+
+	void		(*RunFrame) (void);
+
+	// ServerCommand will be called when an "sv <command>" command is issued on the
+	// server console.
+	// The game can issue gi.argc() / gi.argv() commands to get the rest
+	// of the parameters
+	void		(*ServerCommand) (void);
+
+	//
+	// global variables shared between game and server
+	//
+
+	// The edict array is allocated in the game dll so it
+	// can vary in size from one game to another.
+	// 
+	// The size will be fixed when ge->Init() is called
+	edict_t	*edicts;
+	int			edict_size;
+	int			num_edicts;		// current number, <= max_edicts
+	int			max_edicts;
+} game_export_t;
+
+game_export_t *GetGameAPI (game_import_t *import);
+
+
+// the "gameversion" client command will print this plus compile date
+#define	GAMEVERSION	"baseq2"
+
+//==================================================================
+
+// view pitching times
+#define DAMAGE_TIME		0.5
+#define	FALL_TIME		0.3
+
+
+// edict->spawnflags
+// these are set with checkboxes on each entity in the map editor
+#define	SPAWNFLAG_NOT_EASY			0x00000100
+#define	SPAWNFLAG_NOT_MEDIUM		0x00000200
+#define	SPAWNFLAG_NOT_HARD			0x00000400
+#define	SPAWNFLAG_NOT_DEATHMATCH	0x00000800
+#define	SPAWNFLAG_NOT_COOP			0x00001000
+
+// edict->flags
+#define	FL_FLY					0x00000001
+#define	FL_SWIM					0x00000002	// implied immunity to drowining
+#define FL_IMMUNE_LASER			0x00000004
+#define	FL_INWATER				0x00000008
+#define	FL_GODMODE				0x00000010
+#define	FL_NOTARGET				0x00000020
+#define FL_IMMUNE_SLIME			0x00000040
+#define FL_IMMUNE_LAVA			0x00000080
+#define	FL_PARTIALGROUND		0x00000100	// not all corners are valid
+#define	FL_WATERJUMP			0x00000200	// player jumping out of water
+#define	FL_TEAMSLAVE			0x00000400	// not the first on the team
+#define FL_NO_KNOCKBACK			0x00000800
+#define FL_POWER_ARMOR			0x00001000	// power armor (if any) is active
+#define FL_RESPAWN				0x80000000	// used for item respawning
+
+
+#define	FRAMETIME		0.1
+
+// memory tags to allow dynamic memory to be cleaned up
+#define	TAG_GAME	765		// clear when unloading the dll
+#define	TAG_LEVEL	766		// clear when loading a new level
+
+
+#define MELEE_DISTANCE	80
+
+#define BODY_QUEUE_SIZE		8
+
+typedef enum
+{
+	DAMAGE_NO,
+	DAMAGE_YES,			// will take damage if hit
+	DAMAGE_AIM			// auto targeting recognizes this
+} damage_t;
+
+typedef enum 
+{
+	WEAPON_READY, 
+	WEAPON_ACTIVATING,
+	WEAPON_DROPPING,
+	WEAPON_FIRING
+} weaponstate_t;
+
+typedef enum
+{
+	AMMO_BULLETS,
+	AMMO_SHELLS,
+	AMMO_ROCKETS,
+	AMMO_GRENADES,
+	AMMO_CELLS,
+	AMMO_SLUGS,
+	// RAFAEL
+	AMMO_MAGSLUG,
+	AMMO_TRAP
+} ammo_t;
+
+
+//deadflag
+#define DEAD_NO					0
+#define DEAD_DYING				1
+#define DEAD_DEAD				2
+#define DEAD_RESPAWNABLE		3
+
+//range
+#define RANGE_MELEE				0
+#define RANGE_NEAR				1
+#define RANGE_MID				2
+#define RANGE_FAR				3
+
+//gib types
+#define GIB_ORGANIC				0
+#define GIB_METALLIC			1
+
+//monster ai flags
+#define AI_STAND_GROUND			0x00000001
+#define AI_TEMP_STAND_GROUND	0x00000002
+#define AI_SOUND_TARGET			0x00000004
+#define AI_LOST_SIGHT			0x00000008
+#define AI_PURSUIT_LAST_SEEN	0x00000010
+#define AI_PURSUE_NEXT			0x00000020
+#define AI_PURSUE_TEMP			0x00000040
+#define AI_HOLD_FRAME			0x00000080
+#define AI_GOOD_GUY				0x00000100
+#define AI_BRUTAL				0x00000200
+#define AI_NOSTEP				0x00000400
+#define AI_DUCKED				0x00000800
+#define AI_COMBAT_POINT			0x00001000
+#define AI_MEDIC				0x00002000
+#define AI_RESURRECTING			0x00004000
+
+//monster attack state
+#define AS_STRAIGHT				1
+#define AS_SLIDING				2
+#define	AS_MELEE				3
+#define	AS_MISSILE				4
+
+// armor types
+#define ARMOR_NONE				0
+#define ARMOR_JACKET			1
+#define ARMOR_COMBAT			2
+#define ARMOR_BODY				3
+#define ARMOR_SHARD				4
+
+// power armor types
+#define POWER_ARMOR_NONE		0
+#define POWER_ARMOR_SCREEN		1
+#define POWER_ARMOR_SHIELD		2
+
+// handedness values
+#define RIGHT_HANDED			0
+#define LEFT_HANDED				1
+#define CENTER_HANDED			2
+
+
+// game.serverflags values
+#define SFL_CROSS_TRIGGER_1		0x00000001
+#define SFL_CROSS_TRIGGER_2		0x00000002
+#define SFL_CROSS_TRIGGER_3		0x00000004
+#define SFL_CROSS_TRIGGER_4		0x00000008
+#define SFL_CROSS_TRIGGER_5		0x00000010
+#define SFL_CROSS_TRIGGER_6		0x00000020
+#define SFL_CROSS_TRIGGER_7		0x00000040
+#define SFL_CROSS_TRIGGER_8		0x00000080
+#define SFL_CROSS_TRIGGER_MASK	0x000000ff
+
+
+// noise types for PlayerNoise
+#define PNOISE_SELF				0
+#define PNOISE_WEAPON			1
+#define PNOISE_IMPACT			2
+
+
+// edict->movetype values
+typedef enum
+{
+MOVETYPE_NONE,			// never moves
+MOVETYPE_NOCLIP,		// origin and angles change with no interaction
+MOVETYPE_PUSH,			// no clip to world, push on box contact
+MOVETYPE_STOP,			// no clip to world, stops on box contact
+
+MOVETYPE_WALK,			// gravity
+MOVETYPE_STEP,			// gravity, special edge handling
+MOVETYPE_FLY,
+MOVETYPE_TOSS,			// gravity
+MOVETYPE_FLYMISSILE,	// extra size to monsters
+MOVETYPE_BOUNCE,		// added this (the comma at the end of line)
+// RAFAEL
+MOVETYPE_WALLBOUNCE
+} movetype_t;
+
+
+
+typedef struct
+{
+	int		base_count;
+	int		max_count;
+	float	normal_protection;
+	float	energy_protection;
+	int		armor;
+} gitem_armor_t;
+
+
+// gitem_t->flags
+#define	IT_WEAPON		1		// use makes active weapon
+#define	IT_AMMO			2
+#define IT_ARMOR		4
+#define IT_STAY_COOP	8
+#define IT_KEY			16
+#define IT_POWERUP		32
+
+// gitem_t->weapmodel for weapons indicates model index
+#define WEAP_BLASTER			1 
+#define WEAP_SHOTGUN			2 
+#define WEAP_SUPERSHOTGUN		3 
+#define WEAP_MACHINEGUN			4 
+#define WEAP_CHAINGUN			5 
+#define WEAP_GRENADES			6 
+#define WEAP_GRENADELAUNCHER	7 
+#define WEAP_ROCKETLAUNCHER		8 
+#define WEAP_HYPERBLASTER		9 
+#define WEAP_RAILGUN			10
+#define WEAP_BFG				11
+#define WEAP_PHALANX			12
+#define WEAP_BOOMER				13
+
+typedef struct gitem_t gitem_t;
+typedef struct gitem_t
+{
+	char		*classname;	// spawning name
+	qboolean	(*pickup)(edict_t *ent, edict_t *other);
+	void		(*use)(edict_t *ent, gitem_t *item);
+	void		(*drop)(edict_t *ent, gitem_t *item);
+	void		(*weaponthink)(struct edict_t *ent);
+	char		*pickup_sound;
+	char		*world_model;
+	int			world_model_flags;
+	char		*view_model;
+
+	// client side info
+	char		*icon;
+	char		*pickup_name;	// for printing on pickup
+	int			count_width;		// number of digits to display by icon
+
+	int			quantity;		// for ammo how much, for weapons how much is used per shot
+	char		*ammo;			// for weapons
+	int			flags;			// IT_* flags
+
+	int			weapmodel;		// weapon model index (for weapons)
+
+	void		*info;
+	int			tag;
+
+	char		*precaches;		// string of all models, sounds, and images this item will use
+} gitem_t;
+
+
+
+//
+// this structure is left intact through an entire game
+// it should be initialized at dll load time, and read/written to
+// the server.ssv file for savegames
+//
+typedef struct
+{
+	char		helpmessage1[512];
+	char		helpmessage2[512];
+	int			helpchanged;	// flash F1 icon if non 0, play sound
+								// and increment only if 1, 2, or 3
+
+	gclient_t	*clients;		// [maxclients]
+
+	// can't store spawnpoint in level, because
+	// it would get overwritten by the savegame restore
+	char		spawnpoint[512];	// needed for coop respawns
+
+	// store latched cvars here that we want to get at often
+	int			maxclients;
+	int			maxentities;
+
+	// cross level triggers
+	int			serverflags;
+
+	// items
+	int			num_items;
+
+	qboolean	autosaved;
+} game_locals_t;
+
+
+//
+// this structure is cleared as each map is entered
+// it is read/written to the level.sav file for savegames
+//
+typedef struct
+{
+	int			framenum;
+	float		time;
+
+	char		level_name[MAX_QPATH];	// the descriptive name (Outer Base, etc)
+	char		mapname[MAX_QPATH];		// the server name (base1, etc)
+	char		nextmap[MAX_QPATH];		// go here when fraglimit is hit
+
+	// intermission state
+	float		intermissiontime;		// time the intermission was started
+	char		*changemap;
+	int			exitintermission;
+	vec3_t		intermission_origin;
+	vec3_t		intermission_angle;
+
+	edict_t		*sight_client;	// changed once each frame for coop games
+
+	edict_t		*sight_entity;
+	int			sight_entity_framenum;
+	edict_t		*sound_entity;
+	int			sound_entity_framenum;
+	edict_t		*sound2_entity;
+	int			sound2_entity_framenum;
+
+	int			pic_health;
+
+	int			total_secrets;
+	int			found_secrets;
+
+	int			total_goals;
+	int			found_goals;
+
+	int			total_monsters;
+	int			killed_monsters;
+
+	edict_t		*current_entity;	// entity running from G_RunFrame
+	int			body_que;			// dead bodies
+
+	int			power_cubes;		// ugly necessity for coop
+} level_locals_t;
+
+
+// spawn_temp_t is only used to hold entity field values that
+// can be set from the editor, but aren't actualy present
+// in edict_t during gameplay
+typedef struct
+{
+	// world vars
+	char		*sky;
+	float		skyrotate;
+	vec3_t		skyaxis;
+	char		*nextmap;
+
+	int			lip;
+	int			distance;
+	int			height;
+	char		*noise;
+	float		pausetime;
+	char		*item;
+	char		*gravity;
+
+	float		minyaw;
+	float		maxyaw;
+	float		minpitch;
+	float		maxpitch;
+} spawn_temp_t;
+
+
+typedef struct
+{
+	// fixed data
+	vec3_t		start_origin;
+	vec3_t		start_angles;
+	vec3_t		end_origin;
+	vec3_t		end_angles;
+
+	int			sound_start;
+	int			sound_middle;
+	int			sound_end;
+
+	float		accel;
+	float		speed;
+	float		decel;
+	float		distance;
+
+	float		wait;
+
+	// state data
+	int			state;
+	vec3_t		dir;
+	float		current_speed;
+	float		move_speed;
+	float		next_speed;
+	float		remaining_distance;
+	float		decel_distance;
+	void		(*endfunc)(edict_t *);
+} moveinfo_t;
+
+
+typedef struct
+{
+	void	(*aifunc)(edict_t *self, float dist);
+	float	dist;
+	void	(*thinkfunc)(edict_t *self);
+} mframe_t;
+
+typedef struct
+{
+	int			firstframe;
+	int			lastframe;
+	mframe_t	*frame;
+	void		(*endfunc)(edict_t *self);
+} mmove_t;
+
+typedef struct
+{
+	mmove_t		*currentmove;
+	int			aiflags;
+	int			nextframe;
+	float		scale;
+
+	void		(*stand)(edict_t *self);
+	void		(*idle)(edict_t *self);
+	void		(*search)(edict_t *self);
+	void		(*walk)(edict_t *self);
+	void		(*run)(edict_t *self);
+	void		(*dodge)(edict_t *self, edict_t *other, float eta);
+	void		(*attack)(edict_t *self);
+	void		(*melee)(edict_t *self);
+	void		(*sight)(edict_t *self, edict_t *other);
+	qboolean	(*checkattack)(edict_t *self);
+
+	float		pausetime;
+	float		attack_finished;
+
+	vec3_t		saved_goal;
+	float		search_time;
+	float		trail_time;
+	vec3_t		last_sighting;
+	int			attack_state;
+	int			lefty;
+	float		idle_time;
+	int			linkcount;
+
+	int			power_armor_type;
+	int			power_armor_power;
+} monsterinfo_t;
+
+
+
+extern	game_locals_t	game;
+extern	level_locals_t	level;
+extern	game_import_t	gi;
+extern	game_export_t	globals;
+extern	spawn_temp_t	st;
+
+extern	int	sm_meat_index;
+extern	int	snd_fry;
+
+// means of death
+#define MOD_UNKNOWN			0
+#define MOD_BLASTER			1
+#define MOD_SHOTGUN			2
+#define MOD_SSHOTGUN		3
+#define MOD_MACHINEGUN		4
+#define MOD_CHAINGUN		5
+#define MOD_GRENADE			6
+#define MOD_G_SPLASH		7
+#define MOD_ROCKET			8
+#define MOD_R_SPLASH		9
+#define MOD_HYPERBLASTER	10
+#define MOD_RAILGUN			11
+#define MOD_BFG_LASER		12
+#define MOD_BFG_BLAST		13
+#define MOD_BFG_EFFECT		14
+#define MOD_HANDGRENADE		15
+#define MOD_HG_SPLASH		16
+#define MOD_WATER			17
+#define MOD_SLIME			18
+#define MOD_LAVA			19
+#define MOD_CRUSH			20
+#define MOD_TELEFRAG		21
+#define MOD_FALLING			22
+#define MOD_SUICIDE			23
+#define MOD_HELD_GRENADE	24
+#define MOD_EXPLOSIVE		25
+#define MOD_BARREL			26
+#define MOD_BOMB			27
+#define MOD_EXIT			28
+#define MOD_SPLASH			29
+#define MOD_TARGET_LASER	30
+#define MOD_TRIGGER_HURT	31
+#define MOD_HIT				32
+#define MOD_TARGET_BLASTER	33
+// RAFAEL 14-APR-98
+#define MOD_RIPPER				34
+#define MOD_PHALANX				35
+#define MOD_BRAINTENTACLE		36
+#define MOD_BLASTOFF			37
+#define MOD_GEKK				38
+#define MOD_TRAP				39
+// END 14-APR-98
+#define MOD_FRIENDLY_FIRE	0x8000000
+
+extern	int	meansOfDeath;
+
+
+extern	edict_t			*g_edicts;
+
+#define	FOFS(x) (int)&(((edict_t *)0)->x)
+#define	STOFS(x) (int)&(((spawn_temp_t *)0)->x)
+#define	LLOFS(x) (int)&(((level_locals_t *)0)->x)
+#define	CLOFS(x) (int)&(((gclient_t *)0)->x)
+
+#define qrandom()	((rand () & 0x7fff) / ((float)0x7fff))
+#define crandom()	(2.0 * (qrandom() - 0.5))
+
+extern	cvar_t	*maxentities;
+extern	cvar_t	*deathmatch;
+extern	cvar_t	*coop;
+extern	cvar_t	*dmflags;
+extern	cvar_t	*skill;
+extern	cvar_t	*fraglimit;
+extern	cvar_t	*timelimit;
+extern	cvar_t	*password;
+extern	cvar_t	*spectator_password;
+extern	cvar_t	*g_select_empty;
+extern	cvar_t	*dedicated;
+
+extern	cvar_t	*filterban;
+
+extern	cvar_t	*sv_gravity;
+extern	cvar_t	*sv_maxvelocity;
+
+extern	cvar_t	*gun_x, *gun_y, *gun_z;
+extern	cvar_t	*sv_rollspeed;
+extern	cvar_t	*sv_rollangle;
+
+extern	cvar_t	*run_pitch;
+extern	cvar_t	*run_roll;
+extern	cvar_t	*bob_up;
+extern	cvar_t	*bob_pitch;
+extern	cvar_t	*bob_roll;
+
+extern	cvar_t	*sv_cheats;
+extern	cvar_t	*maxclients;
+extern	cvar_t	*maxspectators;
+
+extern	cvar_t	*flood_msgs;
+extern	cvar_t	*flood_persecond;
+extern	cvar_t	*flood_waitdelay;
+
+extern	cvar_t	*sv_maplist;
+
+#define WORLD	(&g_edicts[0])
+
+// item spawnflags
+#define ITEM_TRIGGER_SPAWN		0x00000001
+#define ITEM_NO_TOUCH			0x00000002
+// 6 bits reserved for editor flags
+// 8 bits used as power cube id bits for coop games
+#define DROPPED_ITEM			0x00010000
+#define	DROPPED_PLAYER_ITEM		0x00020000
+#define ITEM_TARGETS_USED		0x00040000
+
+//
+// fields are needed for spawning from the entity string
+// and saving / loading games
+//
+#define FFL_SPAWNTEMP		1
+#define FFL_NOSPAWN			2
+
+typedef enum {
+	F_INT, 
+	F_FLOAT,
+	F_LSTRING,			// string on disk, pointer in memory, TAG_LEVEL
+	F_GSTRING,			// string on disk, pointer in memory, TAG_GAME
+	F_VECTOR,
+	F_ANGLEHACK,
+	F_EDICT,			// index on disk, pointer in memory
+	F_ITEM,				// index on disk, pointer in memory
+	F_CLIENT,			// index on disk, pointer in memory
+	F_FUNCTION,
+	F_MMOVE,
+	F_IGNORE
+} fieldtype_t;
+
+typedef struct
+{
+	char	*name;
+	int		ofs;
+	fieldtype_t	type;
+	int		flags;
+} field_t;
+
+
+extern	field_t fields[];
+extern	gitem_t	itemlist[];
+
+
+//
+// g_cmds.c
+//
+void Cmd_Help_f (edict_t *ent);
+void Cmd_Score_f (edict_t *ent);
+
+//
+// g_items.c
+//
+void PrecacheItem (gitem_t *it);
+void InitItems (void);
+void SetItemNames (void);
+gitem_t	*FindItem (char *pickup_name);
+gitem_t	*FindItemByClassname (char *classname);
+#define	ITEM_INDEX(x) ((x)-itemlist)
+edict_t *Drop_Item (edict_t *ent, gitem_t *item);
+void SetRespawn (edict_t *ent, float delay);
+void ChangeWeapon (edict_t *ent);
+void SpawnItem (edict_t *ent, gitem_t *item);
+void Think_Weapon (edict_t *ent);
+int ArmorIndex (edict_t *ent);
+int PowerArmorType (edict_t *ent);
+gitem_t	*GetItemByIndex (int index);
+qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count);
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+//
+// g_utils.c
+//
+qboolean	KillBox (edict_t *ent);
+void	G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result);
+edict_t *G_Find (edict_t *from, int fieldofs, char *match);
+edict_t *findradius (edict_t *from, vec3_t org, float rad);
+edict_t *G_PickTarget (char *targetname);
+void	G_UseTargets (edict_t *ent, edict_t *activator);
+void	G_SetMovedir (vec3_t angles, vec3_t movedir);
+
+void	G_InitEdict (edict_t *e);
+edict_t	*G_Spawn (void);
+void	G_FreeEdict (edict_t *e);
+
+void	G_TouchTriggers (edict_t *ent);
+void	G_TouchSolids (edict_t *ent);
+
+char	*G_CopyString (char *in);
+
+float	*tv (float x, float y, float z);
+char	*vtos (vec3_t v);
+
+float vectoyaw (vec3_t vec);
+void vectoangles (vec3_t vec, vec3_t angles);
+
+//
+// g_combat.c
+//
+qboolean OnSameTeam (edict_t *ent1, edict_t *ent2);
+qboolean CanDamage (edict_t *targ, edict_t *inflictor);
+void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod);
+void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod);
+
+// damage flags
+#define DAMAGE_RADIUS			0x00000001	// damage was indirect
+#define DAMAGE_NO_ARMOR			0x00000002	// armour does not protect from this damage
+#define DAMAGE_ENERGY			0x00000004	// damage is from an energy based weapon
+#define DAMAGE_NO_KNOCKBACK		0x00000008	// do not affect velocity, just view angles
+#define DAMAGE_BULLET			0x00000010  // damage is from a bullet (used for ricochets)
+#define DAMAGE_NO_PROTECTION	0x00000020  // armor, shields, invulnerability, and godmode have no effect
+
+#define DEFAULT_BULLET_HSPREAD	300
+#define DEFAULT_BULLET_VSPREAD	500
+#define DEFAULT_SHOTGUN_HSPREAD	1000
+#define DEFAULT_SHOTGUN_VSPREAD	500
+#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT	12
+#define DEFAULT_SHOTGUN_COUNT	12
+#define DEFAULT_SSHOTGUN_COUNT	20
+
+//
+// g_monster.c
+//
+void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype);
+void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype);
+void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype);
+void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype);
+void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype);
+void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype);
+// RAFAEL
+void monster_fire_ionripper (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+void monster_fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype);
+void monster_dabeam (edict_t *self);
+void monster_fire_blueblaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect);
+
+
+void M_droptofloor (edict_t *ent);
+void monster_think (edict_t *self);
+void walkmonster_start (edict_t *self);
+void swimmonster_start (edict_t *self);
+void flymonster_start (edict_t *self);
+void AttackFinished (edict_t *self, float time);
+void monster_death_use (edict_t *self);
+void M_CatagorizePosition (edict_t *ent);
+qboolean M_CheckAttack (edict_t *self);
+void M_FlyCheck (edict_t *self);
+void M_CheckGround (edict_t *ent);
+
+//
+// g_misc.c
+//
+void ThrowHead (edict_t *self, char *gibname, int damage, int type);
+void ThrowClientHead (edict_t *self, int damage);
+void ThrowGib (edict_t *self, char *gibname, int damage, int type);
+void BecomeExplosion1(edict_t *self);
+// RAFAEL
+void ThrowHeadACID (edict_t *self, char *gibname, int damage, int type);
+void ThrowGibACID (edict_t *self, char *gibname, int damage, int type);
+
+//
+// g_ai.c
+//
+void AI_SetSightClient (void);
+
+void ai_stand (edict_t *self, float dist);
+void ai_move (edict_t *self, float dist);
+void ai_walk (edict_t *self, float dist);
+void ai_turn (edict_t *self, float dist);
+void ai_run (edict_t *self, float dist);
+void ai_charge (edict_t *self, float dist);
+int range (edict_t *self, edict_t *other);
+
+void FoundTarget (edict_t *self);
+qboolean infront (edict_t *self, edict_t *other);
+qboolean visible (edict_t *self, edict_t *other);
+qboolean FacingIdeal(edict_t *self);
+
+//
+// g_weapon.c
+//
+void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin);
+qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick);
+void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod);
+void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod);
+void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper);
+void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius);
+void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held);
+void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage);
+void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick);
+void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius);
+// RAFAEL
+void fire_ionripper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect);
+void fire_heat (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage);
+void fire_blueblaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect);
+void fire_plasma (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage);
+void fire_trap (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held);
+
+//
+// g_ptrail.c
+//
+void PlayerTrail_Init (void);
+void PlayerTrail_Add (vec3_t spot);
+void PlayerTrail_New (vec3_t spot);
+edict_t *PlayerTrail_PickFirst (edict_t *self);
+edict_t *PlayerTrail_PickNext (edict_t *self);
+edict_t	*PlayerTrail_LastSpot (void);
+
+//
+// g_client.c
+//
+void respawn (edict_t *ent);
+void BeginIntermission (edict_t *targ);
+void PutClientInServer (edict_t *ent);
+void InitClientPersistant (gclient_t *client);
+void InitClientResp (gclient_t *client);
+void InitBodyQue (void);
+void ClientBeginServerFrame (edict_t *ent);
+
+//
+// g_player.c
+//
+void player_pain (edict_t *self, edict_t *other, float kick, int damage);
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+//
+// g_svcmds.c
+//
+void	ServerCommand (void);
+qboolean SV_FilterPacket (char *from);
+
+//
+// p_view.c
+//
+void ClientEndServerFrame (edict_t *ent);
+
+//
+// p_hud.c
+//
+void MoveClientToIntermission (edict_t *client);
+void G_SetStats (edict_t *ent);
+void G_SetSpectatorStats (edict_t *ent);
+void G_CheckChaseStats (edict_t *ent);
+void ValidateSelectedItem (edict_t *ent);
+void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer);
+
+//
+// g_pweapon.c
+//
+void PlayerNoise(edict_t *who, vec3_t where, int type);
+
+//
+// m_move.c
+//
+qboolean M_CheckBottom (edict_t *ent);
+qboolean M_walkmove (edict_t *ent, float yaw, float dist);
+void M_MoveToGoal (edict_t *ent, float dist);
+void M_ChangeYaw (edict_t *ent);
+
+//
+// g_phys.c
+//
+void G_RunEntity (edict_t *ent);
+
+//
+// g_main.c
+//
+void SaveClientData (void);
+void FetchClientEntData (edict_t *ent);
+
+//
+// g_chase.c
+//
+void UpdateChaseCam(edict_t *ent);
+void ChaseNext(edict_t *ent);
+void ChasePrev(edict_t *ent);
+void GetChaseTarget(edict_t *ent);
+
+//============================================================================
+
+// client_t->anim_priority
+#define	ANIM_BASIC		0		// stand / run
+#define	ANIM_WAVE		1
+#define	ANIM_JUMP		2
+#define	ANIM_PAIN		3
+#define	ANIM_ATTACK		4
+#define	ANIM_DEATH		5
+#define	ANIM_REVERSE	6
+
+
+// client data that stays across multiple level loads
+typedef struct
+{
+	char		userinfo[MAX_INFO_STRING];
+	char		netname[16];
+	int			hand;
+
+	qboolean	connected;			// a loadgame will leave valid entities that
+									// just don't have a connection yet
+
+	// values saved and restored from edicts when changing levels
+	int			health;
+	int			max_health;
+	int			savedFlags;
+
+	int			selected_item;
+	int			inventory[MAX_ITEMS];
+
+	// ammo capacities
+	int			max_bullets;
+	int			max_shells;
+	int			max_rockets;
+	int			max_grenades;
+	int			max_cells;
+	int			max_slugs;
+	// RAFAEL
+	int			max_magslug;
+	int			max_trap;
+
+	gitem_t		*weapon;
+	gitem_t		*lastweapon;
+
+	int			power_cubes;	// used for tracking the cubes in coop games
+	int			score;			// for calculating total unit score in coop games
+
+	int			game_helpchanged;
+	int			helpchanged;
+
+	qboolean	spectator;			// client is a spectator
+} client_persistant_t;
+
+// client data that stays across deathmatch respawns
+typedef struct
+{
+	client_persistant_t	coop_respawn;	// what to set client->pers to on a respawn
+	int			enterframe;			// level.framenum the client entered the game
+	int			score;				// frags, etc
+	vec3_t		cmd_angles;			// angles sent over in the last command
+
+	qboolean	spectator;			// client is a spectator
+} client_respawn_t;
+
+// this structure is cleared on each PutClientInServer(),
+// except for 'client->pers'
+struct gclient_t
+{
+	// known to server
+	player_state_t	ps;				// communicated by server to clients
+	int				ping;
+
+	// private to game
+	client_persistant_t	pers;
+	client_respawn_t	resp;
+	pmove_state_t		old_pmove;	// for detecting out-of-pmove changes
+
+	qboolean	showscores;			// set layout stat
+	qboolean	showinventory;		// set layout stat
+	qboolean	showhelp;
+	qboolean	showhelpicon;
+
+	int			ammo_index;
+
+	int			buttons;
+	int			oldbuttons;
+	int			latched_buttons;
+
+	qboolean	weapon_thunk;
+
+	gitem_t		*newweapon;
+
+	// sum up damage over an entire frame, so
+	// shotgun blasts give a single big kick
+	int			damage_armor;		// damage absorbed by armor
+	int			damage_parmor;		// damage absorbed by power armor
+	int			damage_blood;		// damage taken out of health
+	int			damage_knockback;	// impact damage
+	vec3_t		damage_from;		// origin for vector calculation
+
+	float		killer_yaw;			// when dead, look at killer
+
+	weaponstate_t	weaponstate;
+	vec3_t		kick_angles;	// weapon kicks
+	vec3_t		kick_origin;
+	float		v_dmg_roll, v_dmg_pitch, v_dmg_time;	// damage kicks
+	float		fall_time, fall_value;		// for view drop on fall
+	float		damage_alpha;
+	float		bonus_alpha;
+	vec3_t		damage_blend;
+	vec3_t		v_angle;			// aiming direction
+	float		bobtime;			// so off-ground doesn't change it
+	vec3_t		oldviewangles;
+	vec3_t		oldvelocity;
+
+	float		next_drown_time;
+	int			old_waterlevel;
+	int			breather_sound;
+
+	int			machinegun_shots;	// for weapon raising
+
+	// animation vars
+	int			anim_end;
+	int			anim_priority;
+	qboolean	anim_duck;
+	qboolean	anim_run;
+
+	// powerup timers
+	float		quad_framenum;
+	float		invincible_framenum;
+	float		breather_framenum;
+	float		enviro_framenum;
+
+	qboolean	grenade_blew_up;
+	float		grenade_time;
+	// RAFAEL
+	float		quadfire_framenum;
+	qboolean	trap_blew_up;
+	float		trap_time;
+	
+	int			silencer_shots;
+	int			weapon_sound;
+
+	float		pickup_msg_time;
+
+	float		flood_locktill;		// locked from talking
+	float		flood_when[10];		// when messages were said
+	int			flood_whenhead;		// head pointer for when said
+
+	float		respawn_time;		// can respawn when time > this
+
+	edict_t		*chase_target;		// player we are chasing
+	qboolean	update_chase;		// need to update chase info?
+};
+
+
+struct edict_t
+{
+	entity_state_t	s;
+	gclient_t	*client;	// NULL if not a player
+									// the server expects the first part
+									// of gclient_t to be a player_state_t
+									// but the rest of it is opaque
+
+	qboolean	inuse;
+	int			linkcount;
+
+	// FIXME: move these fields to a server private sv_entity_t
+	link_t		area;				// linked to a division node or leaf
+	
+	int			num_clusters;		// if -1, use headnode instead
+	int			clusternums[MAX_ENT_CLUSTERS];
+	int			headnode;			// unused if num_clusters != -1
+	int			areanum, areanum2;
+
+	//================================
+
+	int			svflags;
+	vec3_t		mins, maxs;
+	vec3_t		absmin, absmax, size;
+	solid_t		solid;
+	int			clipmask;
+	edict_t		*owner;
+
+
+	// DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER
+	// EXPECTS THE FIELDS IN THAT ORDER!
+
+	//================================
+	int			movetype;
+	int			flags;
+
+	char		*model;
+	float		freetime;			// sv.time when the object was freed
+	
+	//
+	// only used locally in game, not by server
+	//
+	char		*message;
+	char		*classname;
+	int			spawnflags;
+
+	float		timestamp;
+
+	float		angle;			// set in qe3, -1 = up, -2 = down
+	char		*target;
+	char		*targetname;
+	char		*killtarget;
+	char		*team;
+	char		*pathtarget;
+	char		*deathtarget;
+	char		*combattarget;
+	edict_t		*target_ent;
+
+	float		speed, accel, decel;
+	vec3_t		movedir;
+	vec3_t		pos1, pos2;
+
+	vec3_t		velocity;
+	vec3_t		avelocity;
+	int			mass;
+	float		air_finished;
+	float		gravity;		// per entity gravity multiplier (1.0 is normal)
+								// use for lowgrav artifact, flares
+
+	edict_t		*goalentity;
+	edict_t		*movetarget;
+	float		yaw_speed;
+	float		ideal_yaw;
+
+	float		nextthink;
+	void		(*prethink) (edict_t *ent);
+	void		(*think)(edict_t *self);
+	void		(*blocked)(edict_t *self, edict_t *other);	//move to moveinfo?
+	void		(*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
+	void		(*use)(edict_t *self, edict_t *other, edict_t *activator);
+	void		(*pain)(edict_t *self, edict_t *other, float kick, int damage);
+	void		(*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+	float		touch_debounce_time;		// are all these legit?  do we need more/less of them?
+	float		pain_debounce_time;
+	float		damage_debounce_time;
+	float		fly_sound_debounce_time;	//move to clientinfo
+	float		last_move_time;
+
+	int			health;
+	int			max_health;
+	int			gib_health;
+	int			deadflag;
+	qboolean	show_hostile;
+
+	float		powerarmor_time;
+
+	char		*map;			// target_changelevel
+
+	int			viewheight;		// height above origin where eyesight is determined
+	int			takedamage;
+	int			dmg;
+	int			radius_dmg;
+	float		dmg_radius;
+	int			sounds;			//make this a spawntemp var?
+	int			count;
+
+	edict_t		*chain;
+	edict_t		*enemy;
+	edict_t		*oldenemy;
+	edict_t		*activator;
+	edict_t		*groundentity;
+	int			groundentity_linkcount;
+	edict_t		*teamchain;
+	edict_t		*teammaster;
+
+	edict_t		*mynoise;		// can go in client only
+	edict_t		*mynoise2;
+
+	int			noise_index;
+	int			noise_index2;
+	float		volume;
+	float		attenuation;
+
+	// timing variables
+	float		wait;
+	float		delay;			// before firing targets
+	float		random;
+
+	float		teleport_time;
+
+	int			watertype;
+	int			waterlevel;
+
+	vec3_t		move_origin;
+	vec3_t		move_angles;
+
+	// move this to clientinfo?
+	int			light_level;
+
+	int			style;			// also used as areaportal number
+
+	gitem_t		*item;			// for bonus items
+
+	// common data blocks
+	moveinfo_t		moveinfo;
+	monsterinfo_t	monsterinfo;
+
+	// RAFAEL
+	int			orders;
+};
--- /dev/null
+++ b/xatrix/m_actor.c
@@ -1,0 +1,592 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_actor.h"
+
+#define	MAX_ACTOR_NAMES		8
+char *actor_names[MAX_ACTOR_NAMES] =
+{
+	"Hellrot",
+	"Tokay",
+	"Killme",
+	"Disruptor",
+	"Adrianator",
+	"Rambear",
+	"Titus",
+	"Bitterman"
+};
+
+
+mframe_t actor_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t actor_move_stand = {FRAME_stand101, FRAME_stand140, actor_frames_stand, NULL};
+
+void actor_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &actor_move_stand;
+
+	// randomize on startup
+	if (level.time < 1.0)
+		self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1));
+}
+
+
+mframe_t actor_frames_walk [] =
+{
+	ai_walk, 0,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t actor_move_walk = {FRAME_walk01, FRAME_walk08, actor_frames_walk, NULL};
+
+void actor_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &actor_move_walk;
+}
+
+
+mframe_t actor_frames_run [] =
+{
+	ai_run, 4,  NULL,
+	ai_run, 15, NULL,
+	ai_run, 15, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 20, NULL,
+	ai_run, 15, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 17, NULL,
+	ai_run, 12, NULL,
+	ai_run, -2, NULL,
+	ai_run, -2, NULL,
+	ai_run, -1, NULL
+};
+mmove_t actor_move_run = {FRAME_run02, FRAME_run07, actor_frames_run, NULL};
+
+void actor_run (edict_t *self)
+{
+	if ((level.time < self->pain_debounce_time) && (!self->enemy))
+	{
+		if (self->movetarget)
+			actor_walk(self);
+		else
+			actor_stand(self);
+		return;
+	}
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		actor_stand(self);
+		return;
+	}
+
+	self->monsterinfo.currentmove = &actor_move_run;
+}
+
+
+mframe_t actor_frames_pain1 [] =
+{
+	ai_move, -5, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 1,  NULL
+};
+mmove_t actor_move_pain1 = {FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run};
+
+mframe_t actor_frames_pain2 [] =
+{
+	ai_move, -4, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t actor_move_pain2 = {FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run};
+
+mframe_t actor_frames_pain3 [] =
+{
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t actor_move_pain3 = {FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run};
+
+mframe_t actor_frames_flipoff [] =
+{
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL
+};
+mmove_t actor_move_flipoff = {FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run};
+
+mframe_t actor_frames_taunt [] =
+{
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL,
+	ai_turn, 0,  NULL
+};
+mmove_t actor_move_taunt = {FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run};
+
+char *messages[] =
+{
+	"Watch it",
+	"#$@*&",
+	"Idiot",
+	"Check your targets"
+};
+
+void actor_pain (edict_t *self, edict_t *other, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+//	gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0);
+
+	if ((other->client) && (qrandom() < 0.4))
+	{
+		vec3_t	v;
+		char	*name;
+
+		VectorSubtract (other->s.origin, self->s.origin, v);
+		self->ideal_yaw = vectoyaw (v);
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &actor_move_flipoff;
+		else
+			self->monsterinfo.currentmove = &actor_move_taunt;
+		name = actor_names[(self - g_edicts)%MAX_ACTOR_NAMES];
+		gi.cprintf (other, PRINT_CHAT, "%s: %s!\n", name, messages[rand()%3]);
+		return;
+	}
+
+	n = rand() % 3;
+	if (n == 0)
+		self->monsterinfo.currentmove = &actor_move_pain1;
+	else if (n == 1)
+		self->monsterinfo.currentmove = &actor_move_pain2;
+	else
+		self->monsterinfo.currentmove = &actor_move_pain3;
+}
+
+
+void actorMachineGun (edict_t *self)
+{
+	vec3_t	start, target;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right, start);
+	if (self->enemy)
+	{
+		if (self->enemy->health > 0)
+		{
+			VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+			target[2] += self->enemy->viewheight;
+		}
+		else
+		{
+			VectorCopy (self->enemy->absmin, target);
+			target[2] += (self->enemy->size[2] / 2);
+		}
+		VectorSubtract (target, start, forward);
+		VectorNormalize (forward);
+	}
+	else
+	{
+		AngleVectors (self->s.angles, forward, NULL, NULL);
+	}
+	monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1);
+}
+
+
+void actor_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t actor_frames_death1 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -13, NULL,
+	ai_move, 14,  NULL,
+	ai_move, 3,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,   NULL
+};
+mmove_t actor_move_death1 = {FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead};
+
+mframe_t actor_frames_death2 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 7,   NULL,
+	ai_move, -6,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 1,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -9,  NULL,
+	ai_move, -13, NULL,
+	ai_move, -13, NULL,
+	ai_move, 0,   NULL
+};
+mmove_t actor_move_death2 = {FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead};
+
+void actor_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= -80)
+	{
+//		gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+//	gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 2;
+	if (n == 0)
+		self->monsterinfo.currentmove = &actor_move_death1;
+	else
+		self->monsterinfo.currentmove = &actor_move_death2;
+}
+
+
+void actor_fire (edict_t *self)
+{
+	actorMachineGun (self);
+
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t actor_frames_attack [] =
+{
+	ai_charge, -2,  actor_fire,
+	ai_charge, -2,  NULL,
+	ai_charge, 3,   NULL,
+	ai_charge, 2,   NULL
+};
+mmove_t actor_move_attack = {FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run};
+
+void actor_attack(edict_t *self)
+{
+	int		n;
+
+	self->monsterinfo.currentmove = &actor_move_attack;
+	n = (rand() & 15) + 3 + 7;
+	self->monsterinfo.pausetime = level.time + n * FRAMETIME;
+}
+
+
+void actor_use (edict_t *self, edict_t *, edict_t *)
+{
+	vec3_t		v;
+
+	self->goalentity = self->movetarget = G_PickTarget(self->target);
+	if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0))
+	{
+		gi.dprintf ("%s has bad target %s at %s\n", self->classname, self->target, vtos(self->s.origin));
+		self->target = NULL;
+		self->monsterinfo.pausetime = 100000000;
+		self->monsterinfo.stand (self);
+		return;
+	}
+
+	VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+	self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
+	self->monsterinfo.walk (self);
+	self->target = NULL;
+}
+
+
+/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32)
+*/
+
+void SP_misc_actor (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->targetname)
+	{
+		gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (!self->target)
+	{
+		gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("players/male/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	if (!self->health)
+		self->health = 100;
+	self->mass = 200;
+
+	self->pain = actor_pain;
+	self->die = actor_die;
+
+	self->monsterinfo.stand = actor_stand;
+	self->monsterinfo.walk = actor_walk;
+	self->monsterinfo.run = actor_run;
+	self->monsterinfo.attack = actor_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+
+	self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &actor_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+
+	// actors always start in a dormant state, they *must* be used to get going
+	self->use = actor_use;
+}
+
+
+/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL
+JUMP			jump in set direction upon reaching this target
+SHOOT			take a single shot at the pathtarget
+ATTACK			attack pathtarget until it or actor is dead 
+
+"target"		next target_actor
+"pathtarget"	target of any action to be taken at this point
+"wait"			amount of time actor should pause at this point
+"message"		actor will "say" this to the player
+
+for JUMP only:
+"speed"			speed thrown forward (default 200)
+"height"		speed thrown upwards (default 200)
+*/
+
+void target_actor_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	vec3_t	v;
+
+	if (other->movetarget != self)
+		return;
+	
+	if (other->enemy)
+		return;
+
+	other->goalentity = other->movetarget = NULL;
+
+	if (self->message)
+	{
+		int		n;
+		edict_t	*ent;
+
+		for (n = 1; n <= game.maxclients; n++)
+		{
+			ent = &g_edicts[n];
+			if (!ent->inuse)
+				continue;
+			gi.cprintf (ent, PRINT_CHAT, "%s: %s\n", actor_names[(other - g_edicts)%MAX_ACTOR_NAMES], self->message);
+		}
+	}
+
+	if (self->spawnflags & 1)		//jump
+	{
+		other->velocity[0] = self->movedir[0] * self->speed;
+		other->velocity[1] = self->movedir[1] * self->speed;
+		
+		if (other->groundentity)
+		{
+			other->groundentity = NULL;
+			other->velocity[2] = self->movedir[2];
+			gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0);
+		}
+	}
+
+	if (self->spawnflags & 2)	//shoot
+	{
+	}
+	else if (self->spawnflags & 4)	//attack
+	{
+		other->enemy = G_PickTarget(self->pathtarget);
+		if (other->enemy)
+		{
+			other->goalentity = other->enemy;
+			if (self->spawnflags & 32)
+				other->monsterinfo.aiflags |= AI_BRUTAL;
+			if (self->spawnflags & 16)
+			{
+				other->monsterinfo.aiflags |= AI_STAND_GROUND;
+				actor_stand (other);
+			}
+			else
+			{
+				actor_run (other);
+			}
+		}
+	}
+
+	if (!(self->spawnflags & 6) && (self->pathtarget))
+	{
+		char *savetarget;
+
+		savetarget = self->target;
+		self->target = self->pathtarget;
+		G_UseTargets (self, other);
+		self->target = savetarget;
+	}
+
+	other->movetarget = G_PickTarget(self->target);
+
+	if (!other->goalentity)
+		other->goalentity = other->movetarget;
+
+	if (!other->movetarget && !other->enemy)
+	{
+		other->monsterinfo.pausetime = level.time + 100000000;
+		other->monsterinfo.stand (other);
+	}
+	else if (other->movetarget == other->goalentity)
+	{
+		VectorSubtract (other->movetarget->s.origin, other->s.origin, v);
+		other->ideal_yaw = vectoyaw (v);
+	}
+}
+
+void SP_target_actor (edict_t *self)
+{
+	if (!self->targetname)
+		gi.dprintf ("%s with no targetname at %s\n", self->classname, vtos(self->s.origin));
+
+	self->solid = SOLID_TRIGGER;
+	self->touch = target_actor_touch;
+	VectorSet (self->mins, -8, -8, -8);
+	VectorSet (self->maxs, 8, 8, 8);
+	self->svflags = SVF_NOCLIENT;
+
+	if (self->spawnflags & 1)
+	{
+		if (!self->speed)
+			self->speed = 200;
+		if (!st.height)
+			st.height = 200;
+		if (self->s.angles[YAW] == 0)
+			self->s.angles[YAW] = 360;
+		G_SetMovedir (self->s.angles, self->movedir);
+		self->movedir[2] = st.height;
+	}
+
+	gi.linkentity (self);
+}
--- /dev/null
+++ b/xatrix/m_actor.h
@@ -1,0 +1,487 @@
+// G:\quake2\baseq2\models/player_y
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak01         	0
+#define FRAME_attak02         	1
+#define FRAME_attak03         	2
+#define FRAME_attak04         	3
+#define FRAME_death101        	4
+#define FRAME_death102        	5
+#define FRAME_death103        	6
+#define FRAME_death104        	7
+#define FRAME_death105        	8
+#define FRAME_death106        	9
+#define FRAME_death107        	10
+#define FRAME_death201        	11
+#define FRAME_death202        	12
+#define FRAME_death203        	13
+#define FRAME_death204        	14
+#define FRAME_death205        	15
+#define FRAME_death206        	16
+#define FRAME_death207        	17
+#define FRAME_death208        	18
+#define FRAME_death209        	19
+#define FRAME_death210        	20
+#define FRAME_death211        	21
+#define FRAME_death212        	22
+#define FRAME_death213        	23
+#define FRAME_death301        	24
+#define FRAME_death302        	25
+#define FRAME_death303        	26
+#define FRAME_death304        	27
+#define FRAME_death305        	28
+#define FRAME_death306        	29
+#define FRAME_death307        	30
+#define FRAME_death308        	31
+#define FRAME_death309        	32
+#define FRAME_death310        	33
+#define FRAME_death311        	34
+#define FRAME_death312        	35
+#define FRAME_death313        	36
+#define FRAME_death314        	37
+#define FRAME_death315        	38
+#define FRAME_flip01          	39
+#define FRAME_flip02          	40
+#define FRAME_flip03          	41
+#define FRAME_flip04          	42
+#define FRAME_flip05          	43
+#define FRAME_flip06          	44
+#define FRAME_flip07          	45
+#define FRAME_flip08          	46
+#define FRAME_flip09          	47
+#define FRAME_flip10          	48
+#define FRAME_flip11          	49
+#define FRAME_flip12          	50
+#define FRAME_flip13          	51
+#define FRAME_flip14          	52
+#define FRAME_grenad01        	53
+#define FRAME_grenad02        	54
+#define FRAME_grenad03        	55
+#define FRAME_grenad04        	56
+#define FRAME_grenad05        	57
+#define FRAME_grenad06        	58
+#define FRAME_grenad07        	59
+#define FRAME_grenad08        	60
+#define FRAME_grenad09        	61
+#define FRAME_grenad10        	62
+#define FRAME_grenad11        	63
+#define FRAME_grenad12        	64
+#define FRAME_grenad13        	65
+#define FRAME_grenad14        	66
+#define FRAME_grenad15        	67
+#define FRAME_jump01          	68
+#define FRAME_jump02          	69
+#define FRAME_jump03          	70
+#define FRAME_jump04          	71
+#define FRAME_jump05          	72
+#define FRAME_jump06          	73
+#define FRAME_pain101         	74
+#define FRAME_pain102         	75
+#define FRAME_pain103         	76
+#define FRAME_pain201         	77
+#define FRAME_pain202         	78
+#define FRAME_pain203         	79
+#define FRAME_pain301         	80
+#define FRAME_pain302         	81
+#define FRAME_pain303         	82
+#define FRAME_push01          	83
+#define FRAME_push02          	84
+#define FRAME_push03          	85
+#define FRAME_push04          	86
+#define FRAME_push05          	87
+#define FRAME_push06          	88
+#define FRAME_push07          	89
+#define FRAME_push08          	90
+#define FRAME_push09          	91
+#define FRAME_run01           	92
+#define FRAME_run02           	93
+#define FRAME_run03           	94
+#define FRAME_run04           	95
+#define FRAME_run05           	96
+#define FRAME_run06           	97
+#define FRAME_run07           	98
+#define FRAME_run08           	99
+#define FRAME_run09           	100
+#define FRAME_run10           	101
+#define FRAME_run11           	102
+#define FRAME_run12           	103
+#define FRAME_runs01          	104
+#define FRAME_runs02          	105
+#define FRAME_runs03          	106
+#define FRAME_runs04          	107
+#define FRAME_runs05          	108
+#define FRAME_runs06          	109
+#define FRAME_runs07          	110
+#define FRAME_runs08          	111
+#define FRAME_runs09          	112
+#define FRAME_runs10          	113
+#define FRAME_runs11          	114
+#define FRAME_runs12          	115
+#define FRAME_salute01        	116
+#define FRAME_salute02        	117
+#define FRAME_salute03        	118
+#define FRAME_salute04        	119
+#define FRAME_salute05        	120
+#define FRAME_salute06        	121
+#define FRAME_salute07        	122
+#define FRAME_salute08        	123
+#define FRAME_salute09        	124
+#define FRAME_salute10        	125
+#define FRAME_salute11        	126
+#define FRAME_salute12        	127
+#define FRAME_stand101        	128
+#define FRAME_stand102        	129
+#define FRAME_stand103        	130
+#define FRAME_stand104        	131
+#define FRAME_stand105        	132
+#define FRAME_stand106        	133
+#define FRAME_stand107        	134
+#define FRAME_stand108        	135
+#define FRAME_stand109        	136
+#define FRAME_stand110        	137
+#define FRAME_stand111        	138
+#define FRAME_stand112        	139
+#define FRAME_stand113        	140
+#define FRAME_stand114        	141
+#define FRAME_stand115        	142
+#define FRAME_stand116        	143
+#define FRAME_stand117        	144
+#define FRAME_stand118        	145
+#define FRAME_stand119        	146
+#define FRAME_stand120        	147
+#define FRAME_stand121        	148
+#define FRAME_stand122        	149
+#define FRAME_stand123        	150
+#define FRAME_stand124        	151
+#define FRAME_stand125        	152
+#define FRAME_stand126        	153
+#define FRAME_stand127        	154
+#define FRAME_stand128        	155
+#define FRAME_stand129        	156
+#define FRAME_stand130        	157
+#define FRAME_stand131        	158
+#define FRAME_stand132        	159
+#define FRAME_stand133        	160
+#define FRAME_stand134        	161
+#define FRAME_stand135        	162
+#define FRAME_stand136        	163
+#define FRAME_stand137        	164
+#define FRAME_stand138        	165
+#define FRAME_stand139        	166
+#define FRAME_stand140        	167
+#define FRAME_stand201        	168
+#define FRAME_stand202        	169
+#define FRAME_stand203        	170
+#define FRAME_stand204        	171
+#define FRAME_stand205        	172
+#define FRAME_stand206        	173
+#define FRAME_stand207        	174
+#define FRAME_stand208        	175
+#define FRAME_stand209        	176
+#define FRAME_stand210        	177
+#define FRAME_stand211        	178
+#define FRAME_stand212        	179
+#define FRAME_stand213        	180
+#define FRAME_stand214        	181
+#define FRAME_stand215        	182
+#define FRAME_stand216        	183
+#define FRAME_stand217        	184
+#define FRAME_stand218        	185
+#define FRAME_stand219        	186
+#define FRAME_stand220        	187
+#define FRAME_stand221        	188
+#define FRAME_stand222        	189
+#define FRAME_stand223        	190
+#define FRAME_swim01          	191
+#define FRAME_swim02          	192
+#define FRAME_swim03          	193
+#define FRAME_swim04          	194
+#define FRAME_swim05          	195
+#define FRAME_swim06          	196
+#define FRAME_swim07          	197
+#define FRAME_swim08          	198
+#define FRAME_swim09          	199
+#define FRAME_swim10          	200
+#define FRAME_swim11          	201
+#define FRAME_swim12          	202
+#define FRAME_sw_atk01        	203
+#define FRAME_sw_atk02        	204
+#define FRAME_sw_atk03        	205
+#define FRAME_sw_atk04        	206
+#define FRAME_sw_atk05        	207
+#define FRAME_sw_atk06        	208
+#define FRAME_sw_pan01        	209
+#define FRAME_sw_pan02        	210
+#define FRAME_sw_pan03        	211
+#define FRAME_sw_pan04        	212
+#define FRAME_sw_pan05        	213
+#define FRAME_sw_std01        	214
+#define FRAME_sw_std02        	215
+#define FRAME_sw_std03        	216
+#define FRAME_sw_std04        	217
+#define FRAME_sw_std05        	218
+#define FRAME_sw_std06        	219
+#define FRAME_sw_std07        	220
+#define FRAME_sw_std08        	221
+#define FRAME_sw_std09        	222
+#define FRAME_sw_std10        	223
+#define FRAME_sw_std11        	224
+#define FRAME_sw_std12        	225
+#define FRAME_sw_std13        	226
+#define FRAME_sw_std14        	227
+#define FRAME_sw_std15        	228
+#define FRAME_sw_std16        	229
+#define FRAME_sw_std17        	230
+#define FRAME_sw_std18        	231
+#define FRAME_sw_std19        	232
+#define FRAME_sw_std20        	233
+#define FRAME_taunt01         	234
+#define FRAME_taunt02         	235
+#define FRAME_taunt03         	236
+#define FRAME_taunt04         	237
+#define FRAME_taunt05         	238
+#define FRAME_taunt06         	239
+#define FRAME_taunt07         	240
+#define FRAME_taunt08         	241
+#define FRAME_taunt09         	242
+#define FRAME_taunt10         	243
+#define FRAME_taunt11         	244
+#define FRAME_taunt12         	245
+#define FRAME_taunt13         	246
+#define FRAME_taunt14         	247
+#define FRAME_taunt15         	248
+#define FRAME_taunt16         	249
+#define FRAME_taunt17         	250
+#define FRAME_walk01          	251
+#define FRAME_walk02          	252
+#define FRAME_walk03          	253
+#define FRAME_walk04          	254
+#define FRAME_walk05          	255
+#define FRAME_walk06          	256
+#define FRAME_walk07          	257
+#define FRAME_walk08          	258
+#define FRAME_walk09          	259
+#define FRAME_walk10          	260
+#define FRAME_walk11          	261
+#define FRAME_wave01          	262
+#define FRAME_wave02          	263
+#define FRAME_wave03          	264
+#define FRAME_wave04          	265
+#define FRAME_wave05          	266
+#define FRAME_wave06          	267
+#define FRAME_wave07          	268
+#define FRAME_wave08          	269
+#define FRAME_wave09          	270
+#define FRAME_wave10          	271
+#define FRAME_wave11          	272
+#define FRAME_wave12          	273
+#define FRAME_wave13          	274
+#define FRAME_wave14          	275
+#define FRAME_wave15          	276
+#define FRAME_wave16          	277
+#define FRAME_wave17          	278
+#define FRAME_wave18          	279
+#define FRAME_wave19          	280
+#define FRAME_wave20          	281
+#define FRAME_wave21          	282
+#define FRAME_bl_atk01        	283
+#define FRAME_bl_atk02        	284
+#define FRAME_bl_atk03        	285
+#define FRAME_bl_atk04        	286
+#define FRAME_bl_atk05        	287
+#define FRAME_bl_atk06        	288
+#define FRAME_bl_flp01        	289
+#define FRAME_bl_flp02        	290
+#define FRAME_bl_flp13        	291
+#define FRAME_bl_flp14        	292
+#define FRAME_bl_flp15        	293
+#define FRAME_bl_jmp01        	294
+#define FRAME_bl_jmp02        	295
+#define FRAME_bl_jmp03        	296
+#define FRAME_bl_jmp04        	297
+#define FRAME_bl_jmp05        	298
+#define FRAME_bl_jmp06        	299
+#define FRAME_bl_pn101        	300
+#define FRAME_bl_pn102        	301
+#define FRAME_bl_pn103        	302
+#define FRAME_bl_pn201        	303
+#define FRAME_bl_pn202        	304
+#define FRAME_bl_pn203        	305
+#define FRAME_bl_pn301        	306
+#define FRAME_bl_pn302        	307
+#define FRAME_bl_pn303        	308
+#define FRAME_bl_psh08        	309
+#define FRAME_bl_psh09        	310
+#define FRAME_bl_run01        	311
+#define FRAME_bl_run02        	312
+#define FRAME_bl_run03        	313
+#define FRAME_bl_run04        	314
+#define FRAME_bl_run05        	315
+#define FRAME_bl_run06        	316
+#define FRAME_bl_run07        	317
+#define FRAME_bl_run08        	318
+#define FRAME_bl_run09        	319
+#define FRAME_bl_run10        	320
+#define FRAME_bl_run11        	321
+#define FRAME_bl_run12        	322
+#define FRAME_bl_rns03        	323
+#define FRAME_bl_rns04        	324
+#define FRAME_bl_rns05        	325
+#define FRAME_bl_rns06        	326
+#define FRAME_bl_rns07        	327
+#define FRAME_bl_rns08        	328
+#define FRAME_bl_rns09        	329
+#define FRAME_bl_sal10        	330
+#define FRAME_bl_sal11        	331
+#define FRAME_bl_sal12        	332
+#define FRAME_bl_std01        	333
+#define FRAME_bl_std02        	334
+#define FRAME_bl_std03        	335
+#define FRAME_bl_std04        	336
+#define FRAME_bl_std05        	337
+#define FRAME_bl_std06        	338
+#define FRAME_bl_std07        	339
+#define FRAME_bl_std08        	340
+#define FRAME_bl_std09        	341
+#define FRAME_bl_std10        	342
+#define FRAME_bl_std11        	343
+#define FRAME_bl_std12        	344
+#define FRAME_bl_std13        	345
+#define FRAME_bl_std14        	346
+#define FRAME_bl_std15        	347
+#define FRAME_bl_std16        	348
+#define FRAME_bl_std17        	349
+#define FRAME_bl_std18        	350
+#define FRAME_bl_std19        	351
+#define FRAME_bl_std20        	352
+#define FRAME_bl_std21        	353
+#define FRAME_bl_std22        	354
+#define FRAME_bl_std23        	355
+#define FRAME_bl_std24        	356
+#define FRAME_bl_std25        	357
+#define FRAME_bl_std26        	358
+#define FRAME_bl_std27        	359
+#define FRAME_bl_std28        	360
+#define FRAME_bl_std29        	361
+#define FRAME_bl_std30        	362
+#define FRAME_bl_std31        	363
+#define FRAME_bl_std32        	364
+#define FRAME_bl_std33        	365
+#define FRAME_bl_std34        	366
+#define FRAME_bl_std35        	367
+#define FRAME_bl_std36        	368
+#define FRAME_bl_std37        	369
+#define FRAME_bl_std38        	370
+#define FRAME_bl_std39        	371
+#define FRAME_bl_std40        	372
+#define FRAME_bl_swm01        	373
+#define FRAME_bl_swm02        	374
+#define FRAME_bl_swm03        	375
+#define FRAME_bl_swm04        	376
+#define FRAME_bl_swm05        	377
+#define FRAME_bl_swm06        	378
+#define FRAME_bl_swm07        	379
+#define FRAME_bl_swm08        	380
+#define FRAME_bl_swm09        	381
+#define FRAME_bl_swm10        	382
+#define FRAME_bl_swm11        	383
+#define FRAME_bl_swm12        	384
+#define FRAME_bl_swk01        	385
+#define FRAME_bl_swk02        	386
+#define FRAME_bl_swk03        	387
+#define FRAME_bl_swk04        	388
+#define FRAME_bl_swk05        	389
+#define FRAME_bl_swk06        	390
+#define FRAME_bl_swp01        	391
+#define FRAME_bl_swp02        	392
+#define FRAME_bl_swp03        	393
+#define FRAME_bl_swp04        	394
+#define FRAME_bl_swp05        	395
+#define FRAME_bl_sws01        	396
+#define FRAME_bl_sws02        	397
+#define FRAME_bl_sws03        	398
+#define FRAME_bl_sws04        	399
+#define FRAME_bl_sws05        	400
+#define FRAME_bl_sws06        	401
+#define FRAME_bl_sws07        	402
+#define FRAME_bl_sws08        	403
+#define FRAME_bl_sws09        	404
+#define FRAME_bl_sws10        	405
+#define FRAME_bl_sws11        	406
+#define FRAME_bl_sws12        	407
+#define FRAME_bl_sws13        	408
+#define FRAME_bl_sws14        	409
+#define FRAME_bl_tau14        	410
+#define FRAME_bl_tau15        	411
+#define FRAME_bl_tau16        	412
+#define FRAME_bl_tau17        	413
+#define FRAME_bl_wlk01        	414
+#define FRAME_bl_wlk02        	415
+#define FRAME_bl_wlk03        	416
+#define FRAME_bl_wlk04        	417
+#define FRAME_bl_wlk05        	418
+#define FRAME_bl_wlk06        	419
+#define FRAME_bl_wlk07        	420
+#define FRAME_bl_wlk08        	421
+#define FRAME_bl_wlk09        	422
+#define FRAME_bl_wlk10        	423
+#define FRAME_bl_wlk11        	424
+#define FRAME_bl_wav19        	425
+#define FRAME_bl_wav20        	426
+#define FRAME_bl_wav21        	427
+#define FRAME_cr_atk01        	428
+#define FRAME_cr_atk02        	429
+#define FRAME_cr_atk03        	430
+#define FRAME_cr_atk04        	431
+#define FRAME_cr_atk05        	432
+#define FRAME_cr_atk06        	433
+#define FRAME_cr_atk07        	434
+#define FRAME_cr_atk08        	435
+#define FRAME_cr_pan01        	436
+#define FRAME_cr_pan02        	437
+#define FRAME_cr_pan03        	438
+#define FRAME_cr_pan04        	439
+#define FRAME_cr_std01        	440
+#define FRAME_cr_std02        	441
+#define FRAME_cr_std03        	442
+#define FRAME_cr_std04        	443
+#define FRAME_cr_std05        	444
+#define FRAME_cr_std06        	445
+#define FRAME_cr_std07        	446
+#define FRAME_cr_std08        	447
+#define FRAME_cr_wlk01        	448
+#define FRAME_cr_wlk02        	449
+#define FRAME_cr_wlk03        	450
+#define FRAME_cr_wlk04        	451
+#define FRAME_cr_wlk05        	452
+#define FRAME_cr_wlk06        	453
+#define FRAME_cr_wlk07        	454
+#define FRAME_crbl_a01        	455
+#define FRAME_crbl_a02        	456
+#define FRAME_crbl_a03        	457
+#define FRAME_crbl_a04        	458
+#define FRAME_crbl_a05        	459
+#define FRAME_crbl_a06        	460
+#define FRAME_crbl_a07        	461
+#define FRAME_crbl_p01        	462
+#define FRAME_crbl_p02        	463
+#define FRAME_crbl_p03        	464
+#define FRAME_crbl_p04        	465
+#define FRAME_crbl_s01        	466
+#define FRAME_crbl_s02        	467
+#define FRAME_crbl_s03        	468
+#define FRAME_crbl_s04        	469
+#define FRAME_crbl_s05        	470
+#define FRAME_crbl_s06        	471
+#define FRAME_crbl_s07        	472
+#define FRAME_crbl_s08        	473
+#define FRAME_crbl_w01        	474
+#define FRAME_crbl_w02        	475
+#define FRAME_crbl_w03        	476
+#define FRAME_crbl_w04        	477
+#define FRAME_crbl_w05        	478
+#define FRAME_crbl_w06        	479
+#define FRAME_crbl_w07        	480
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_berserk.c
@@ -1,0 +1,434 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_berserk.h"
+
+
+static int sound_pain;
+static int sound_die;
+static int sound_idle;
+static int sound_punch;
+static int sound_sight;
+static int sound_search;
+
+void berserk_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void berserk_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+void berserk_fidget (edict_t *self);
+mframe_t berserk_frames_stand [] =
+{
+	ai_stand, 0, berserk_fidget,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t berserk_move_stand = {FRAME_stand1, FRAME_stand5, berserk_frames_stand, NULL};
+
+void berserk_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &berserk_move_stand;
+}
+
+mframe_t berserk_frames_stand_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t berserk_move_stand_fidget = {FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand};
+
+void berserk_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() > 0.15)
+		return;
+
+	self->monsterinfo.currentmove = &berserk_move_stand_fidget;
+	gi.sound (self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+mframe_t berserk_frames_walk [] =
+{
+	ai_walk, 9.1, NULL,
+	ai_walk, 6.3, NULL,
+	ai_walk, 4.9, NULL,
+	ai_walk, 6.7, NULL,
+	ai_walk, 6.0, NULL,
+	ai_walk, 8.2, NULL,
+	ai_walk, 7.2, NULL,
+	ai_walk, 6.1, NULL,
+	ai_walk, 4.9, NULL,
+	ai_walk, 4.7, NULL,
+	ai_walk, 4.7, NULL,
+	ai_walk, 4.8, NULL
+};
+mmove_t berserk_move_walk = {FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, NULL};
+
+void berserk_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &berserk_move_walk;
+}
+
+/*
+
+  *****************************
+  SKIPPED THIS FOR NOW!
+  *****************************
+
+   Running -> Arm raised in air
+
+void()	berserk_runb1	=[	$r_att1 ,	berserk_runb2	] {ai_run(21);};
+void()	berserk_runb2	=[	$r_att2 ,	berserk_runb3	] {ai_run(11);};
+void()	berserk_runb3	=[	$r_att3 ,	berserk_runb4	] {ai_run(21);};
+void()	berserk_runb4	=[	$r_att4 ,	berserk_runb5	] {ai_run(25);};
+void()	berserk_runb5	=[	$r_att5 ,	berserk_runb6	] {ai_run(18);};
+void()	berserk_runb6	=[	$r_att6 ,	berserk_runb7	] {ai_run(19);};
+// running with arm in air : start loop
+void()	berserk_runb7	=[	$r_att7 ,	berserk_runb8	] {ai_run(21);};
+void()	berserk_runb8	=[	$r_att8 ,	berserk_runb9	] {ai_run(11);};
+void()	berserk_runb9	=[	$r_att9 ,	berserk_runb10	] {ai_run(21);};
+void()	berserk_runb10	=[	$r_att10 ,	berserk_runb11	] {ai_run(25);};
+void()	berserk_runb11	=[	$r_att11 ,	berserk_runb12	] {ai_run(18);};
+void()	berserk_runb12	=[	$r_att12 ,	berserk_runb7	] {ai_run(19);};
+// running with arm in air : end loop
+*/
+
+
+mframe_t berserk_frames_run1 [] =
+{
+	ai_run, 21, NULL,
+	ai_run, 11, NULL,
+	ai_run, 21, NULL,
+	ai_run, 25, NULL,
+	ai_run, 18, NULL,
+	ai_run, 19, NULL
+};
+mmove_t berserk_move_run1 = {FRAME_run1, FRAME_run6, berserk_frames_run1, NULL};
+
+void berserk_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &berserk_move_stand;
+	else
+		self->monsterinfo.currentmove = &berserk_move_run1;
+}
+
+
+void berserk_attack_spike (edict_t *self)
+{
+	static	vec3_t	aim = {MELEE_DISTANCE, 0, -24};
+	fire_hit (self, aim, (15 + (rand() % 6)), 400);		//	Faster attack -- upwards and backwards
+}
+
+
+void berserk_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
+}
+
+mframe_t berserk_frames_attack_spike [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, berserk_swing,
+		ai_charge, 0, berserk_attack_spike,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t berserk_move_attack_spike = {FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run};
+
+
+void berserk_attack_club (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4);
+	fire_hit (self, aim, (5 + (rand() % 6)), 400);		// Slower attack
+}
+
+mframe_t berserk_frames_attack_club [] =
+{	
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, berserk_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, berserk_attack_club,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t berserk_move_attack_club = {FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run};
+
+
+void berserk_strike (edict_t *)
+{
+	//FIXME play impact sound
+}
+
+
+mframe_t berserk_frames_attack_strike [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_swing,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, berserk_strike,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 9.7, NULL,
+	ai_move, 13.6, NULL
+};
+	
+mmove_t berserk_move_attack_strike = {FRAME_att_c21, FRAME_att_c34, berserk_frames_attack_strike, berserk_run};
+
+
+void berserk_melee (edict_t *self)
+{
+	if ((rand() % 2) == 0)
+		self->monsterinfo.currentmove = &berserk_move_attack_spike;
+	else
+		self->monsterinfo.currentmove = &berserk_move_attack_club;
+}
+
+
+/*
+void() 	berserk_atke1	=[	$r_attb1,	berserk_atke2	] {ai_run(9);};
+void() 	berserk_atke2	=[	$r_attb2,	berserk_atke3	] {ai_run(6);};
+void() 	berserk_atke3	=[	$r_attb3,	berserk_atke4	] {ai_run(18.4);};
+void() 	berserk_atke4	=[	$r_attb4,	berserk_atke5	] {ai_run(25);};
+void() 	berserk_atke5	=[	$r_attb5,	berserk_atke6	] {ai_run(14);};
+void() 	berserk_atke6	=[	$r_attb6,	berserk_atke7	] {ai_run(20);};
+void() 	berserk_atke7	=[	$r_attb7,	berserk_atke8	] {ai_run(8.5);};
+void() 	berserk_atke8	=[	$r_attb8,	berserk_atke9	] {ai_run(3);};
+void() 	berserk_atke9	=[	$r_attb9,	berserk_atke10	] {ai_run(17.5);};
+void() 	berserk_atke10	=[	$r_attb10,	berserk_atke11	] {ai_run(17);};
+void() 	berserk_atke11	=[	$r_attb11,	berserk_atke12	] {ai_run(9);};
+void() 	berserk_atke12	=[	$r_attb12,	berserk_atke13	] {ai_run(25);};
+void() 	berserk_atke13	=[	$r_attb13,	berserk_atke14	] {ai_run(3.7);};
+void() 	berserk_atke14	=[	$r_attb14,	berserk_atke15	] {ai_run(2.6);};
+void() 	berserk_atke15	=[	$r_attb15,	berserk_atke16	] {ai_run(19);};
+void() 	berserk_atke16	=[	$r_attb16,	berserk_atke17	] {ai_run(25);};
+void() 	berserk_atke17	=[	$r_attb17,	berserk_atke18	] {ai_run(19.6);};
+void() 	berserk_atke18	=[	$r_attb18,	berserk_run1	] {ai_run(7.8);};
+*/
+
+
+mframe_t berserk_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_pain1 = {FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run};
+
+
+mframe_t berserk_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_pain2 = {FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run};
+
+void berserk_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if ((damage < 20) || (qrandom() < 0.5))
+		self->monsterinfo.currentmove = &berserk_move_pain1;
+	else
+		self->monsterinfo.currentmove = &berserk_move_pain2;
+}
+
+
+void berserk_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+mframe_t berserk_frames_death1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+	
+};
+mmove_t berserk_move_death1 = {FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead};
+
+
+mframe_t berserk_frames_death2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t berserk_move_death2 = {FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead};
+
+
+void berserk_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	if (damage >= 50)
+		self->monsterinfo.currentmove = &berserk_move_death1;
+	else
+		self->monsterinfo.currentmove = &berserk_move_death2;
+}
+
+
+/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_berserk (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// pre-caches
+	sound_pain  = gi.soundindex ("berserk/berpain2.wav");
+	sound_die   = gi.soundindex ("berserk/berdeth2.wav");
+	sound_idle  = gi.soundindex ("berserk/beridle1.wav");
+	sound_punch = gi.soundindex ("berserk/attack.wav");
+	sound_search = gi.soundindex ("berserk/bersrch1.wav");
+	sound_sight = gi.soundindex ("berserk/sight.wav");
+
+	self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 240;
+	self->gib_health = -60;
+	self->mass = 250;
+
+	self->pain = berserk_pain;
+	self->die = berserk_die;
+
+	self->monsterinfo.stand = berserk_stand;
+	self->monsterinfo.walk = berserk_walk;
+	self->monsterinfo.run = berserk_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = NULL;
+	self->monsterinfo.melee = berserk_melee;
+	self->monsterinfo.sight = berserk_sight;
+	self->monsterinfo.search = berserk_search;
+
+	self->monsterinfo.currentmove = &berserk_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	gi.linkentity (self);
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_berserk.h
@@ -1,0 +1,250 @@
+// G:\quake2\baseq2\models/monsters/berserk
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_standb1         	5
+#define FRAME_standb2         	6
+#define FRAME_standb3         	7
+#define FRAME_standb4         	8
+#define FRAME_standb5         	9
+#define FRAME_standb6         	10
+#define FRAME_standb7         	11
+#define FRAME_standb8         	12
+#define FRAME_standb9         	13
+#define FRAME_standb10        	14
+#define FRAME_standb11        	15
+#define FRAME_standb12        	16
+#define FRAME_standb13        	17
+#define FRAME_standb14        	18
+#define FRAME_standb15        	19
+#define FRAME_standb16        	20
+#define FRAME_standb17        	21
+#define FRAME_standb18        	22
+#define FRAME_standb19        	23
+#define FRAME_standb20        	24
+#define FRAME_walkc1          	25
+#define FRAME_walkc2          	26
+#define FRAME_walkc3          	27
+#define FRAME_walkc4          	28
+#define FRAME_walkc5          	29
+#define FRAME_walkc6          	30
+#define FRAME_walkc7          	31
+#define FRAME_walkc8          	32
+#define FRAME_walkc9          	33
+#define FRAME_walkc10         	34
+#define FRAME_walkc11         	35
+#define FRAME_run1            	36
+#define FRAME_run2            	37
+#define FRAME_run3            	38
+#define FRAME_run4            	39
+#define FRAME_run5            	40
+#define FRAME_run6            	41
+#define FRAME_att_a1          	42
+#define FRAME_att_a2          	43
+#define FRAME_att_a3          	44
+#define FRAME_att_a4          	45
+#define FRAME_att_a5          	46
+#define FRAME_att_a6          	47
+#define FRAME_att_a7          	48
+#define FRAME_att_a8          	49
+#define FRAME_att_a9          	50
+#define FRAME_att_a10         	51
+#define FRAME_att_a11         	52
+#define FRAME_att_a12         	53
+#define FRAME_att_a13         	54
+#define FRAME_att_b1          	55
+#define FRAME_att_b2          	56
+#define FRAME_att_b3          	57
+#define FRAME_att_b4          	58
+#define FRAME_att_b5          	59
+#define FRAME_att_b6          	60
+#define FRAME_att_b7          	61
+#define FRAME_att_b8          	62
+#define FRAME_att_b9          	63
+#define FRAME_att_b10         	64
+#define FRAME_att_b11         	65
+#define FRAME_att_b12         	66
+#define FRAME_att_b13         	67
+#define FRAME_att_b14         	68
+#define FRAME_att_b15         	69
+#define FRAME_att_b16         	70
+#define FRAME_att_b17         	71
+#define FRAME_att_b18         	72
+#define FRAME_att_b19         	73
+#define FRAME_att_b20         	74
+#define FRAME_att_b21         	75
+#define FRAME_att_c1          	76
+#define FRAME_att_c2          	77
+#define FRAME_att_c3          	78
+#define FRAME_att_c4          	79
+#define FRAME_att_c5          	80
+#define FRAME_att_c6          	81
+#define FRAME_att_c7          	82
+#define FRAME_att_c8          	83
+#define FRAME_att_c9          	84
+#define FRAME_att_c10         	85
+#define FRAME_att_c11         	86
+#define FRAME_att_c12         	87
+#define FRAME_att_c13         	88
+#define FRAME_att_c14         	89
+#define FRAME_att_c15         	90
+#define FRAME_att_c16         	91
+#define FRAME_att_c17         	92
+#define FRAME_att_c18         	93
+#define FRAME_att_c19         	94
+#define FRAME_att_c20         	95
+#define FRAME_att_c21         	96
+#define FRAME_att_c22         	97
+#define FRAME_att_c23         	98
+#define FRAME_att_c24         	99
+#define FRAME_att_c25         	100
+#define FRAME_att_c26         	101
+#define FRAME_att_c27         	102
+#define FRAME_att_c28         	103
+#define FRAME_att_c29         	104
+#define FRAME_att_c30         	105
+#define FRAME_att_c31         	106
+#define FRAME_att_c32         	107
+#define FRAME_att_c33         	108
+#define FRAME_att_c34         	109
+#define FRAME_r_att1          	110
+#define FRAME_r_att2          	111
+#define FRAME_r_att3          	112
+#define FRAME_r_att4          	113
+#define FRAME_r_att5          	114
+#define FRAME_r_att6          	115
+#define FRAME_r_att7          	116
+#define FRAME_r_att8          	117
+#define FRAME_r_att9          	118
+#define FRAME_r_att10         	119
+#define FRAME_r_att11         	120
+#define FRAME_r_att12         	121
+#define FRAME_r_att13         	122
+#define FRAME_r_att14         	123
+#define FRAME_r_att15         	124
+#define FRAME_r_att16         	125
+#define FRAME_r_att17         	126
+#define FRAME_r_att18         	127
+#define FRAME_r_attb1         	128
+#define FRAME_r_attb2         	129
+#define FRAME_r_attb3         	130
+#define FRAME_r_attb4         	131
+#define FRAME_r_attb5         	132
+#define FRAME_r_attb6         	133
+#define FRAME_r_attb7         	134
+#define FRAME_r_attb8         	135
+#define FRAME_r_attb9         	136
+#define FRAME_r_attb10        	137
+#define FRAME_r_attb11        	138
+#define FRAME_r_attb12        	139
+#define FRAME_r_attb13        	140
+#define FRAME_r_attb14        	141
+#define FRAME_r_attb15        	142
+#define FRAME_r_attb16        	143
+#define FRAME_r_attb17        	144
+#define FRAME_r_attb18        	145
+#define FRAME_slam1           	146
+#define FRAME_slam2           	147
+#define FRAME_slam3           	148
+#define FRAME_slam4           	149
+#define FRAME_slam5           	150
+#define FRAME_slam6           	151
+#define FRAME_slam7           	152
+#define FRAME_slam8           	153
+#define FRAME_slam9           	154
+#define FRAME_slam10          	155
+#define FRAME_slam11          	156
+#define FRAME_slam12          	157
+#define FRAME_slam13          	158
+#define FRAME_slam14          	159
+#define FRAME_slam15          	160
+#define FRAME_slam16          	161
+#define FRAME_slam17          	162
+#define FRAME_slam18          	163
+#define FRAME_slam19          	164
+#define FRAME_slam20          	165
+#define FRAME_slam21          	166
+#define FRAME_slam22          	167
+#define FRAME_slam23          	168
+#define FRAME_duck1           	169
+#define FRAME_duck2           	170
+#define FRAME_duck3           	171
+#define FRAME_duck4           	172
+#define FRAME_duck5           	173
+#define FRAME_duck6           	174
+#define FRAME_duck7           	175
+#define FRAME_duck8           	176
+#define FRAME_duck9           	177
+#define FRAME_duck10          	178
+#define FRAME_fall1           	179
+#define FRAME_fall2           	180
+#define FRAME_fall3           	181
+#define FRAME_fall4           	182
+#define FRAME_fall5           	183
+#define FRAME_fall6           	184
+#define FRAME_fall7           	185
+#define FRAME_fall8           	186
+#define FRAME_fall9           	187
+#define FRAME_fall10          	188
+#define FRAME_fall11          	189
+#define FRAME_fall12          	190
+#define FRAME_fall13          	191
+#define FRAME_fall14          	192
+#define FRAME_fall15          	193
+#define FRAME_fall16          	194
+#define FRAME_fall17          	195
+#define FRAME_fall18          	196
+#define FRAME_fall19          	197
+#define FRAME_fall20          	198
+#define FRAME_painc1          	199
+#define FRAME_painc2          	200
+#define FRAME_painc3          	201
+#define FRAME_painc4          	202
+#define FRAME_painb1          	203
+#define FRAME_painb2          	204
+#define FRAME_painb3          	205
+#define FRAME_painb4          	206
+#define FRAME_painb5          	207
+#define FRAME_painb6          	208
+#define FRAME_painb7          	209
+#define FRAME_painb8          	210
+#define FRAME_painb9          	211
+#define FRAME_painb10         	212
+#define FRAME_painb11         	213
+#define FRAME_painb12         	214
+#define FRAME_painb13         	215
+#define FRAME_painb14         	216
+#define FRAME_painb15         	217
+#define FRAME_painb16         	218
+#define FRAME_painb17         	219
+#define FRAME_painb18         	220
+#define FRAME_painb19         	221
+#define FRAME_painb20         	222
+#define FRAME_death1          	223
+#define FRAME_death2          	224
+#define FRAME_death3          	225
+#define FRAME_death4          	226
+#define FRAME_death5          	227
+#define FRAME_death6          	228
+#define FRAME_death7          	229
+#define FRAME_death8          	230
+#define FRAME_death9          	231
+#define FRAME_death10         	232
+#define FRAME_death11         	233
+#define FRAME_death12         	234
+#define FRAME_death13         	235
+#define FRAME_deathc1         	236
+#define FRAME_deathc2         	237
+#define FRAME_deathc3         	238
+#define FRAME_deathc4         	239
+#define FRAME_deathc5         	240
+#define FRAME_deathc6         	241
+#define FRAME_deathc7         	242
+#define FRAME_deathc8         	243
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_boss2.c
@@ -1,0 +1,654 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss2.h"
+
+void BossExplode (edict_t *self);
+
+qboolean infront (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+
+void boss2_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
+}
+
+void boss2_run (edict_t *self);
+void boss2_stand (edict_t *self);
+void boss2_dead (edict_t *self);
+void boss2_attack (edict_t *self);
+void boss2_attack_mg (edict_t *self);
+void boss2_reattack_mg (edict_t *self);
+void boss2_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+void Boss2Rocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+//1
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
+
+//2
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
+
+//3
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
+
+//4
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right, start);
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_rocket (self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
+}	
+
+void boss2_firebullet_right (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
+}	
+
+void boss2_firebullet_left (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+	
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
+}	
+
+void Boss2MachineGun (edict_t *self)
+{
+/*	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+
+	flash_number = MZ2_BOSS2_MACHINEGUN_1 + (self->s.frame - FRAME_attack10);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	monster_fire_bullet (self, start, dir, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+*/
+	boss2_firebullet_left(self);
+	boss2_firebullet_right(self);
+}	
+
+
+mframe_t boss2_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	boss2_move_stand = {FRAME_stand30, FRAME_stand50, boss2_frames_stand, NULL};
+
+mframe_t boss2_frames_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t boss2_move_fidget = {FRAME_stand1, FRAME_stand30, boss2_frames_fidget, NULL};
+
+mframe_t boss2_frames_walk [] =
+{
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	8,	NULL
+};
+mmove_t boss2_move_walk = {FRAME_walk1, FRAME_walk20, boss2_frames_walk, NULL};
+
+
+mframe_t boss2_frames_run [] =
+{
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL,
+	ai_run,	8,	NULL
+};
+mmove_t boss2_move_run = {FRAME_walk1, FRAME_walk20, boss2_frames_run, NULL};
+
+mframe_t boss2_frames_attack_pre_mg [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	boss2_attack_mg
+};
+mmove_t boss2_move_attack_pre_mg = {FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, NULL};
+
+
+// Loop this
+mframe_t boss2_frames_attack_mg [] =
+{
+	ai_charge,	1,	Boss2MachineGun,
+	ai_charge,	1,	Boss2MachineGun,
+	ai_charge,	1,	Boss2MachineGun,
+	ai_charge,	1,	Boss2MachineGun,
+	ai_charge,	1,	Boss2MachineGun,
+	ai_charge,	1,	boss2_reattack_mg
+};
+mmove_t boss2_move_attack_mg = {FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, NULL};
+
+mframe_t boss2_frames_attack_post_mg [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t boss2_move_attack_post_mg = {FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run};
+
+mframe_t boss2_frames_attack_rocket [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_move,	-20,	Boss2Rocket,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t boss2_move_attack_rocket = {FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run};
+
+mframe_t boss2_frames_pain_heavy [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss2_move_pain_heavy = {FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run};
+
+mframe_t boss2_frames_pain_light [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss2_move_pain_light = {FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run};
+
+mframe_t boss2_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode
+};
+mmove_t boss2_move_death = {FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead};
+
+void boss2_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &boss2_move_stand;
+}
+
+void boss2_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &boss2_move_stand;
+	else
+		self->monsterinfo.currentmove = &boss2_move_run;
+}
+
+void boss2_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &boss2_move_walk;
+}
+
+void boss2_attack (edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+	
+	if (range <= 125)
+	{
+		self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+	}
+	else 
+	{
+		if (qrandom() <= 0.6)
+			self->monsterinfo.currentmove = &boss2_move_attack_pre_mg;
+		else
+			self->monsterinfo.currentmove = &boss2_move_attack_rocket;
+	}
+}
+
+void boss2_attack_mg (edict_t *self)
+{
+	self->monsterinfo.currentmove = &boss2_move_attack_mg;
+}
+
+void boss2_reattack_mg (edict_t *self)
+{
+	if ( infront(self, self->enemy) )
+		if (qrandom() <= 0.7)
+			self->monsterinfo.currentmove = &boss2_move_attack_mg;
+		else
+			self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+	else
+		self->monsterinfo.currentmove = &boss2_move_attack_post_mg;
+}
+
+
+void boss2_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+// American wanted these at no attenuation
+	if (damage < 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_light;
+	}
+	else if (damage < 30)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_light;
+	}
+	else 
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
+		self->monsterinfo.currentmove = &boss2_move_pain_heavy;
+	}
+}
+
+void boss2_dead (edict_t *self)
+{
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void boss2_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &boss2_move_death;
+/*
+	int		n;
+
+	self->s.sound = 0;
+	// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &boss2_move_death;
+*/
+}
+
+qboolean Boss2_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+	
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.8;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+
+/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_boss2 (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("bosshovr/bhvpain1.wav");
+	sound_pain2 = gi.soundindex ("bosshovr/bhvpain2.wav");
+	sound_pain3 = gi.soundindex ("bosshovr/bhvpain3.wav");
+	sound_death = gi.soundindex ("bosshovr/bhvdeth1.wav");
+	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
+
+	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss2/tris.md2");
+	VectorSet (self->mins, -56, -56, 0);
+	VectorSet (self->maxs, 56, 56, 80);
+
+	self->health = 2000;
+	self->gib_health = -200;
+	self->mass = 1000;
+
+	self->flags |= FL_IMMUNE_LASER;
+
+	self->pain = boss2_pain;
+	self->die = boss2_die;
+
+	self->monsterinfo.stand = boss2_stand;
+	self->monsterinfo.walk = boss2_walk;
+	self->monsterinfo.run = boss2_run;
+	self->monsterinfo.attack = boss2_attack;
+	self->monsterinfo.search = boss2_search;
+	self->monsterinfo.checkattack = Boss2_CheckAttack;
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &boss2_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_boss2.h
@@ -1,0 +1,187 @@
+// G:\quake2\baseq2\models/monsters/boss2
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand30         	0
+#define FRAME_stand31         	1
+#define FRAME_stand32         	2
+#define FRAME_stand33         	3
+#define FRAME_stand34         	4
+#define FRAME_stand35         	5
+#define FRAME_stand36         	6
+#define FRAME_stand37         	7
+#define FRAME_stand38         	8
+#define FRAME_stand39         	9
+#define FRAME_stand40         	10
+#define FRAME_stand41         	11
+#define FRAME_stand42         	12
+#define FRAME_stand43         	13
+#define FRAME_stand44         	14
+#define FRAME_stand45         	15
+#define FRAME_stand46         	16
+#define FRAME_stand47         	17
+#define FRAME_stand48         	18
+#define FRAME_stand49         	19
+#define FRAME_stand50         	20
+#define FRAME_stand1          	21
+#define FRAME_stand2          	22
+#define FRAME_stand3          	23
+#define FRAME_stand4          	24
+#define FRAME_stand5          	25
+#define FRAME_stand6          	26
+#define FRAME_stand7          	27
+#define FRAME_stand8          	28
+#define FRAME_stand9          	29
+#define FRAME_stand10         	30
+#define FRAME_stand11         	31
+#define FRAME_stand12         	32
+#define FRAME_stand13         	33
+#define FRAME_stand14         	34
+#define FRAME_stand15         	35
+#define FRAME_stand16         	36
+#define FRAME_stand17         	37
+#define FRAME_stand18         	38
+#define FRAME_stand19         	39
+#define FRAME_stand20         	40
+#define FRAME_stand21         	41
+#define FRAME_stand22         	42
+#define FRAME_stand23         	43
+#define FRAME_stand24         	44
+#define FRAME_stand25         	45
+#define FRAME_stand26         	46
+#define FRAME_stand27         	47
+#define FRAME_stand28         	48
+#define FRAME_stand29         	49
+#define FRAME_walk1           	50
+#define FRAME_walk2           	51
+#define FRAME_walk3           	52
+#define FRAME_walk4           	53
+#define FRAME_walk5           	54
+#define FRAME_walk6           	55
+#define FRAME_walk7           	56
+#define FRAME_walk8           	57
+#define FRAME_walk9           	58
+#define FRAME_walk10          	59
+#define FRAME_walk11          	60
+#define FRAME_walk12          	61
+#define FRAME_walk13          	62
+#define FRAME_walk14          	63
+#define FRAME_walk15          	64
+#define FRAME_walk16          	65
+#define FRAME_walk17          	66
+#define FRAME_walk18          	67
+#define FRAME_walk19          	68
+#define FRAME_walk20          	69
+#define FRAME_attack1         	70
+#define FRAME_attack2         	71
+#define FRAME_attack3         	72
+#define FRAME_attack4         	73
+#define FRAME_attack5         	74
+#define FRAME_attack6         	75
+#define FRAME_attack7         	76
+#define FRAME_attack8         	77
+#define FRAME_attack9         	78
+#define FRAME_attack10        	79
+#define FRAME_attack11        	80
+#define FRAME_attack12        	81
+#define FRAME_attack13        	82
+#define FRAME_attack14        	83
+#define FRAME_attack15        	84
+#define FRAME_attack16        	85
+#define FRAME_attack17        	86
+#define FRAME_attack18        	87
+#define FRAME_attack19        	88
+#define FRAME_attack20        	89
+#define FRAME_attack21        	90
+#define FRAME_attack22        	91
+#define FRAME_attack23        	92
+#define FRAME_attack24        	93
+#define FRAME_attack25        	94
+#define FRAME_attack26        	95
+#define FRAME_attack27        	96
+#define FRAME_attack28        	97
+#define FRAME_attack29        	98
+#define FRAME_attack30        	99
+#define FRAME_attack31        	100
+#define FRAME_attack32        	101
+#define FRAME_attack33        	102
+#define FRAME_attack34        	103
+#define FRAME_attack35        	104
+#define FRAME_attack36        	105
+#define FRAME_attack37        	106
+#define FRAME_attack38        	107
+#define FRAME_attack39        	108
+#define FRAME_attack40        	109
+#define FRAME_pain2           	110
+#define FRAME_pain3           	111
+#define FRAME_pain4           	112
+#define FRAME_pain5           	113
+#define FRAME_pain6           	114
+#define FRAME_pain7           	115
+#define FRAME_pain8           	116
+#define FRAME_pain9           	117
+#define FRAME_pain10          	118
+#define FRAME_pain11          	119
+#define FRAME_pain12          	120
+#define FRAME_pain13          	121
+#define FRAME_pain14          	122
+#define FRAME_pain15          	123
+#define FRAME_pain16          	124
+#define FRAME_pain17          	125
+#define FRAME_pain18          	126
+#define FRAME_pain19          	127
+#define FRAME_pain20          	128
+#define FRAME_pain21          	129
+#define FRAME_pain22          	130
+#define FRAME_pain23          	131
+#define FRAME_death2          	132
+#define FRAME_death3          	133
+#define FRAME_death4          	134
+#define FRAME_death5          	135
+#define FRAME_death6          	136
+#define FRAME_death7          	137
+#define FRAME_death8          	138
+#define FRAME_death9          	139
+#define FRAME_death10         	140
+#define FRAME_death11         	141
+#define FRAME_death12         	142
+#define FRAME_death13         	143
+#define FRAME_death14         	144
+#define FRAME_death15         	145
+#define FRAME_death16         	146
+#define FRAME_death17         	147
+#define FRAME_death18         	148
+#define FRAME_death19         	149
+#define FRAME_death20         	150
+#define FRAME_death21         	151
+#define FRAME_death22         	152
+#define FRAME_death23         	153
+#define FRAME_death24         	154
+#define FRAME_death25         	155
+#define FRAME_death26         	156
+#define FRAME_death27         	157
+#define FRAME_death28         	158
+#define FRAME_death29         	159
+#define FRAME_death30         	160
+#define FRAME_death31         	161
+#define FRAME_death32         	162
+#define FRAME_death33         	163
+#define FRAME_death34         	164
+#define FRAME_death35         	165
+#define FRAME_death36         	166
+#define FRAME_death37         	167
+#define FRAME_death38         	168
+#define FRAME_death39         	169
+#define FRAME_death40         	170
+#define FRAME_death41         	171
+#define FRAME_death42         	172
+#define FRAME_death43         	173
+#define FRAME_death44         	174
+#define FRAME_death45         	175
+#define FRAME_death46         	176
+#define FRAME_death47         	177
+#define FRAME_death48         	178
+#define FRAME_death49         	179
+#define FRAME_death50         	180
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_boss3.c
@@ -1,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss32.h"
+
+void Use_Boss3 (edict_t *ent, edict_t *, edict_t *)
+{
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_BOSSTPORT);
+	gi.WritePosition (ent->s.origin);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+	G_FreeEdict (ent);
+}
+
+void Think_Boss3Stand (edict_t *ent)
+{
+	if (ent->s.frame == FRAME_stand260)
+		ent->s.frame = FRAME_stand201;
+	else
+		ent->s.frame++;
+	ent->nextthink = level.time + FRAMETIME;
+}
+
+/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90)
+
+Just stands and cycles in one place until targeted, then teleports away.
+*/
+void SP_monster_boss3_stand (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->model = "models/monsters/boss3/rider/tris.md2";
+	self->s.modelindex = gi.modelindex (self->model);
+	self->s.frame = FRAME_stand201;
+
+	gi.soundindex ("misc/bigtele.wav");
+
+	VectorSet (self->mins, -32, -32, 0);
+	VectorSet (self->maxs, 32, 32, 90);
+
+	self->use = Use_Boss3;
+	self->think = Think_Boss3Stand;
+	self->nextthink = level.time + FRAMETIME;
+	gi.linkentity (self);
+}
--- /dev/null
+++ b/xatrix/m_boss31.c
@@ -1,0 +1,720 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss31.h"
+
+extern SP_monster_makron (edict_t *self);
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_idle;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_search2;
+static int	sound_search3;
+static int	sound_attack1;
+static int	sound_attack2;
+static int	sound_firegun;
+static int	sound_step_left;
+static int	sound_step_right;
+static int	sound_death_hit;
+
+void BossExplode (edict_t *self);
+void MakronToss (edict_t *self);
+
+
+void jorg_search (edict_t *self)
+{
+	float r;
+
+	r = qrandom();
+
+	if (r <= 0.3)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else if (r <= 0.6)
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0);
+}
+
+
+void jorg_dead (edict_t *self);
+void jorgBFG (edict_t *self);
+void jorgMachineGun (edict_t *self);
+void jorg_firebullet (edict_t *self);
+void jorg_reattack1(edict_t *self);
+void jorg_attack1(edict_t *self);
+void jorg_idle(edict_t *self);
+void jorg_step_left(edict_t *self);
+void jorg_step_right(edict_t *self);
+void jorg_death_hit(edict_t *self);
+
+//
+// stand
+//
+
+mframe_t jorg_frames_stand []=
+{
+	ai_stand, 0, jorg_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 19, NULL,
+	ai_stand, 11, jorg_step_left,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 6, NULL,
+	ai_stand, 9, jorg_step_right,
+	ai_stand, 0, NULL,		// 40
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, -2, NULL,
+	ai_stand, -17, jorg_step_left,
+	ai_stand, 0, NULL,
+	ai_stand, -12, NULL,		// 50
+	ai_stand, -14, jorg_step_right	// 51
+};
+mmove_t	jorg_move_stand = {FRAME_stand01, FRAME_stand51, jorg_frames_stand, NULL};
+
+void jorg_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_NORM,0);
+}
+
+void jorg_death_hit (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM,0);
+}
+
+
+void jorg_step_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0);
+}
+
+void jorg_step_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0);
+}
+
+
+void jorg_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &jorg_move_stand;
+}
+
+mframe_t jorg_frames_run [] =
+{
+	ai_run, 17,	jorg_step_left,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 10,	NULL,
+	ai_run, 33,	jorg_step_right,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 0,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 9,	NULL
+};
+mmove_t	jorg_move_run = {FRAME_walk06, FRAME_walk19, jorg_frames_run, NULL};
+
+//
+// walk
+//
+
+mframe_t jorg_frames_start_walk [] =
+{
+	ai_walk,	5,	NULL,
+	ai_walk,	6,	NULL,
+	ai_walk,	7,	NULL,
+	ai_walk,	9,	NULL,
+	ai_walk,	15,	NULL
+};
+mmove_t jorg_move_start_walk = {FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, NULL};
+
+mframe_t jorg_frames_walk [] =
+{
+	ai_walk, 17,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 12,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 10,	NULL,
+	ai_walk, 33,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 9,	NULL
+};
+mmove_t	jorg_move_walk = {FRAME_walk06, FRAME_walk19, jorg_frames_walk, NULL};
+
+mframe_t jorg_frames_end_walk [] =
+{
+	ai_walk,	11,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	8,	NULL,
+	ai_walk,	-8,	NULL
+};
+mmove_t jorg_move_end_walk = {FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, NULL};
+
+void jorg_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &jorg_move_walk;
+}
+
+void jorg_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &jorg_move_stand;
+	else
+		self->monsterinfo.currentmove = &jorg_move_run;
+}
+
+mframe_t jorg_frames_pain3 [] =
+{
+	ai_move,	-28,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-3,	jorg_step_left,
+	ai_move,	-9,	NULL,
+	ai_move,	0,	jorg_step_right,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-7,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-11,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	11,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	10,	NULL,
+	ai_move,	7,	jorg_step_left,
+	ai_move,	17,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	jorg_step_right
+};
+mmove_t jorg_move_pain3 = {FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run};
+
+mframe_t jorg_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_pain2 = {FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run};
+
+mframe_t jorg_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_pain1 = {FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run};
+
+mframe_t jorg_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 30
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 40
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	MakronToss,
+	ai_move,	0,	BossExplode		// 50
+};
+mmove_t jorg_move_death = {FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead};
+
+mframe_t jorg_frames_attack2 []=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	jorgBFG,		
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_attack2 = {FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run};
+
+mframe_t jorg_frames_start_attack1 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t jorg_move_start_attack1 = {FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1};
+
+mframe_t jorg_frames_attack1[]=
+{
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet,
+	ai_charge,	0,	jorg_firebullet
+};
+mmove_t jorg_move_attack1 = {FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1};
+
+mframe_t jorg_frames_end_attack1[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t jorg_move_end_attack1 = {FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run};
+
+void jorg_reattack1(edict_t *self)
+{
+	if (visible(self, self->enemy))
+		if (qrandom() < 0.9)
+			self->monsterinfo.currentmove = &jorg_move_attack1;
+		else
+		{
+			self->s.sound = 0;
+			self->monsterinfo.currentmove = &jorg_move_end_attack1;	
+		}
+	else
+	{
+		self->s.sound = 0;
+		self->monsterinfo.currentmove = &jorg_move_end_attack1;	
+	}
+}
+
+void jorg_attack1(edict_t *self)
+{
+	self->monsterinfo.currentmove = &jorg_move_attack1;
+}
+
+void jorg_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+	
+	self->s.sound = 0;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames if he takes little damage
+	if (damage <= 40)
+		if (qrandom()<=0.6)
+			return;
+
+	/* 
+	If he's entering his attack1 or using attack1, lessen the chance of him
+	going into pain
+	*/
+	
+	if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108) )
+		if (qrandom() <= 0.005)
+			return;
+
+	if ( (self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114) )
+		if (qrandom() <= 0.00005)
+			return;
+
+
+	if ( (self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208) )
+		if (qrandom() <= 0.005)
+			return;
+
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 50)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_pain1;
+	}
+	else if (damage <= 100)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_pain2;
+	}
+	else
+	{
+		if (qrandom() <= 0.3)
+		{
+			gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0);
+			self->monsterinfo.currentmove = &jorg_move_pain3;
+		}
+	}
+};
+
+void jorgBFG (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_BFG_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
+	/*void monster_fire_bfg (edict_t *self, 
+							 vec3_t start, 
+							 vec3_t aimdir, 
+							 int damage, 
+							 int speed, 
+							 int kick, 
+							 float damage_radius, 
+							 int flashtype)*/
+	monster_fire_bfg (self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1);
+}	
+
+void jorg_firebullet_right (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1);
+}	
+
+void jorg_firebullet_left (edict_t *self)
+{
+	vec3_t	forward, right, target;
+	vec3_t	start;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right, start);
+
+	VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+	VectorSubtract (target, start, forward);
+	VectorNormalize (forward);
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1);
+}	
+
+void jorg_firebullet (edict_t *self)
+{
+	jorg_firebullet_left(self);
+	jorg_firebullet_right(self);
+};
+
+void jorg_attack(edict_t *self)
+{
+	vec3_t	vec;
+	
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+
+	if (qrandom() <= 0.75)
+	{
+		gi.sound (self, CHAN_VOICE, sound_attack1, 1, ATTN_NORM,0);
+		self->s.sound = gi.soundindex ("boss3/w_loop.wav");
+		self->monsterinfo.currentmove = &jorg_move_start_attack1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &jorg_move_attack2;
+	}
+}
+
+void jorg_dead (edict_t */*self*/)
+{
+/*
+	edict_t	*tempent;
+	//VectorSet (self->mins, -16, -16, -24);
+	//VectorSet (self->maxs, 16, 16, -8);
+	
+	// Jorg is on modelindex2. Do not clear him.
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->nextthink = 0;
+	gi.linkentity (self);
+
+	tempent = G_Spawn();
+	VectorCopy (self->s.origin, tempent->s.origin);
+	VectorCopy (self->s.angles, tempent->s.angles);
+	tempent->killtarget = self->killtarget;
+	tempent->target = self->target;
+	tempent->activator = self->enemy;
+	self->killtarget = 0;
+	self->target = 0;
+	SP_monster_makron (tempent);
+*/
+}
+
+
+void jorg_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->s.sound = 0;
+	self->count = 0;
+	self->monsterinfo.currentmove = &jorg_move_death;
+}
+
+qboolean Jorg_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+	
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.2;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+void MakronPrecache (void);
+
+/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_jorg (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("boss3/bs3pain1.wav");
+	sound_pain2 = gi.soundindex ("boss3/bs3pain2.wav");
+	sound_pain3 = gi.soundindex ("boss3/bs3pain3.wav");
+	sound_death = gi.soundindex ("boss3/bs3deth1.wav");
+	sound_attack1 = gi.soundindex ("boss3/bs3atck1.wav");
+	sound_attack2 = gi.soundindex ("boss3/bs3atck2.wav");
+	sound_search1 = gi.soundindex ("boss3/bs3srch1.wav");
+	sound_search2 = gi.soundindex ("boss3/bs3srch2.wav");
+	sound_search3 = gi.soundindex ("boss3/bs3srch3.wav");
+	sound_idle = gi.soundindex ("boss3/bs3idle1.wav");
+	sound_step_left = gi.soundindex ("boss3/step1.wav");
+	sound_step_right = gi.soundindex ("boss3/step2.wav");
+	sound_firegun = gi.soundindex ("boss3/xfire.wav");
+	sound_death_hit = gi.soundindex ("boss3/d_hit.wav");
+
+	MakronPrecache ();
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	self->s.modelindex2 = gi.modelindex ("models/monsters/boss3/jorg/tris.md2");
+	VectorSet (self->mins, -80, -80, 0);
+	VectorSet (self->maxs, 80, 80, 140);
+
+	self->health = 3000;
+	self->gib_health = -2000;
+	self->mass = 1000;
+
+	self->pain = jorg_pain;
+	self->die = jorg_die;
+	self->monsterinfo.stand = jorg_stand;
+	self->monsterinfo.walk = jorg_walk;
+	self->monsterinfo.run = jorg_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = jorg_attack;
+	self->monsterinfo.search = jorg_search;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+	self->monsterinfo.checkattack = Jorg_CheckAttack;
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &jorg_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+}
--- /dev/null
+++ b/xatrix/m_boss31.h
@@ -1,0 +1,194 @@
+// G:\quake2\baseq2\models/monsters/boss3/jorg
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak201        	18
+#define FRAME_attak202        	19
+#define FRAME_attak203        	20
+#define FRAME_attak204        	21
+#define FRAME_attak205        	22
+#define FRAME_attak206        	23
+#define FRAME_attak207        	24
+#define FRAME_attak208        	25
+#define FRAME_attak209        	26
+#define FRAME_attak210        	27
+#define FRAME_attak211        	28
+#define FRAME_attak212        	29
+#define FRAME_attak213        	30
+#define FRAME_death01         	31
+#define FRAME_death02         	32
+#define FRAME_death03         	33
+#define FRAME_death04         	34
+#define FRAME_death05         	35
+#define FRAME_death06         	36
+#define FRAME_death07         	37
+#define FRAME_death08         	38
+#define FRAME_death09         	39
+#define FRAME_death10         	40
+#define FRAME_death11         	41
+#define FRAME_death12         	42
+#define FRAME_death13         	43
+#define FRAME_death14         	44
+#define FRAME_death15         	45
+#define FRAME_death16         	46
+#define FRAME_death17         	47
+#define FRAME_death18         	48
+#define FRAME_death19         	49
+#define FRAME_death20         	50
+#define FRAME_death21         	51
+#define FRAME_death22         	52
+#define FRAME_death23         	53
+#define FRAME_death24         	54
+#define FRAME_death25         	55
+#define FRAME_death26         	56
+#define FRAME_death27         	57
+#define FRAME_death28         	58
+#define FRAME_death29         	59
+#define FRAME_death30         	60
+#define FRAME_death31         	61
+#define FRAME_death32         	62
+#define FRAME_death33         	63
+#define FRAME_death34         	64
+#define FRAME_death35         	65
+#define FRAME_death36         	66
+#define FRAME_death37         	67
+#define FRAME_death38         	68
+#define FRAME_death39         	69
+#define FRAME_death40         	70
+#define FRAME_death41         	71
+#define FRAME_death42         	72
+#define FRAME_death43         	73
+#define FRAME_death44         	74
+#define FRAME_death45         	75
+#define FRAME_death46         	76
+#define FRAME_death47         	77
+#define FRAME_death48         	78
+#define FRAME_death49         	79
+#define FRAME_death50         	80
+#define FRAME_pain101         	81
+#define FRAME_pain102         	82
+#define FRAME_pain103         	83
+#define FRAME_pain201         	84
+#define FRAME_pain202         	85
+#define FRAME_pain203         	86
+#define FRAME_pain301         	87
+#define FRAME_pain302         	88
+#define FRAME_pain303         	89
+#define FRAME_pain304         	90
+#define FRAME_pain305         	91
+#define FRAME_pain306         	92
+#define FRAME_pain307         	93
+#define FRAME_pain308         	94
+#define FRAME_pain309         	95
+#define FRAME_pain310         	96
+#define FRAME_pain311         	97
+#define FRAME_pain312         	98
+#define FRAME_pain313         	99
+#define FRAME_pain314         	100
+#define FRAME_pain315         	101
+#define FRAME_pain316         	102
+#define FRAME_pain317         	103
+#define FRAME_pain318         	104
+#define FRAME_pain319         	105
+#define FRAME_pain320         	106
+#define FRAME_pain321         	107
+#define FRAME_pain322         	108
+#define FRAME_pain323         	109
+#define FRAME_pain324         	110
+#define FRAME_pain325         	111
+#define FRAME_stand01         	112
+#define FRAME_stand02         	113
+#define FRAME_stand03         	114
+#define FRAME_stand04         	115
+#define FRAME_stand05         	116
+#define FRAME_stand06         	117
+#define FRAME_stand07         	118
+#define FRAME_stand08         	119
+#define FRAME_stand09         	120
+#define FRAME_stand10         	121
+#define FRAME_stand11         	122
+#define FRAME_stand12         	123
+#define FRAME_stand13         	124
+#define FRAME_stand14         	125
+#define FRAME_stand15         	126
+#define FRAME_stand16         	127
+#define FRAME_stand17         	128
+#define FRAME_stand18         	129
+#define FRAME_stand19         	130
+#define FRAME_stand20         	131
+#define FRAME_stand21         	132
+#define FRAME_stand22         	133
+#define FRAME_stand23         	134
+#define FRAME_stand24         	135
+#define FRAME_stand25         	136
+#define FRAME_stand26         	137
+#define FRAME_stand27         	138
+#define FRAME_stand28         	139
+#define FRAME_stand29         	140
+#define FRAME_stand30         	141
+#define FRAME_stand31         	142
+#define FRAME_stand32         	143
+#define FRAME_stand33         	144
+#define FRAME_stand34         	145
+#define FRAME_stand35         	146
+#define FRAME_stand36         	147
+#define FRAME_stand37         	148
+#define FRAME_stand38         	149
+#define FRAME_stand39         	150
+#define FRAME_stand40         	151
+#define FRAME_stand41         	152
+#define FRAME_stand42         	153
+#define FRAME_stand43         	154
+#define FRAME_stand44         	155
+#define FRAME_stand45         	156
+#define FRAME_stand46         	157
+#define FRAME_stand47         	158
+#define FRAME_stand48         	159
+#define FRAME_stand49         	160
+#define FRAME_stand50         	161
+#define FRAME_stand51         	162
+#define FRAME_walk01          	163
+#define FRAME_walk02          	164
+#define FRAME_walk03          	165
+#define FRAME_walk04          	166
+#define FRAME_walk05          	167
+#define FRAME_walk06          	168
+#define FRAME_walk07          	169
+#define FRAME_walk08          	170
+#define FRAME_walk09          	171
+#define FRAME_walk10          	172
+#define FRAME_walk11          	173
+#define FRAME_walk12          	174
+#define FRAME_walk13          	175
+#define FRAME_walk14          	176
+#define FRAME_walk15          	177
+#define FRAME_walk16          	178
+#define FRAME_walk17          	179
+#define FRAME_walk18          	180
+#define FRAME_walk19          	181
+#define FRAME_walk20          	182
+#define FRAME_walk21          	183
+#define FRAME_walk22          	184
+#define FRAME_walk23          	185
+#define FRAME_walk24          	186
+#define FRAME_walk25          	187
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_boss32.c
@@ -1,0 +1,885 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_boss32.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+void MakronRailgun (edict_t *self);
+void MakronSaveloc (edict_t *self);
+void MakronHyperblaster (edict_t *self);
+void makron_step_left (edict_t *self);
+void makron_step_right (edict_t *self);
+void makronBFG (edict_t *self);
+void makron_dead (edict_t *self);
+
+static int	sound_pain4;
+static int	sound_pain5;
+static int	sound_pain6;
+static int	sound_death;
+static int	sound_step_left;
+static int	sound_step_right;
+static int	sound_attack_bfg;
+static int	sound_brainsplorch;
+static int	sound_prerailgun;
+static int	sound_popup;
+static int	sound_taunt1;
+static int	sound_taunt2;
+static int	sound_taunt3;
+static int	sound_hit;
+
+void makron_taunt (edict_t *self)
+{
+	float r;
+
+	r=qrandom();
+	if (r <= 0.3)
+		gi.sound (self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0);
+	else if (r <= 0.6)
+		gi.sound (self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0);
+	else
+		gi.sound (self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0);
+}
+
+//
+// stand
+//
+
+mframe_t makron_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 40
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 50
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL		// 60
+};
+mmove_t	makron_move_stand = {FRAME_stand201, FRAME_stand260, makron_frames_stand, NULL};
+	
+void makron_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &makron_move_stand;
+}
+
+mframe_t makron_frames_run [] =
+{
+	ai_run, 3,	makron_step_left,
+	ai_run, 12,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 8,	NULL,
+	ai_run, 8,	makron_step_right,
+	ai_run, 6,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 9,	NULL,
+	ai_run, 6,	NULL,
+	ai_run, 12,	NULL
+};
+mmove_t	makron_move_run = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL};
+
+void makron_hit (edict_t *self)
+{
+	gi.sound (self, CHAN_AUTO, sound_hit, 1, ATTN_NONE,0);
+}
+
+void makron_popup (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_popup, 1, ATTN_NONE,0);
+}
+
+void makron_step_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_left, 1, ATTN_NORM,0);
+}
+
+void makron_step_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step_right, 1, ATTN_NORM,0);
+}
+
+void makron_brainsplorch (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM,0);
+}
+
+void makron_prerailgun (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM,0);
+}
+
+
+mframe_t makron_frames_walk [] =
+{
+	ai_walk, 3,	makron_step_left,
+	ai_walk, 12,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 8,	NULL,
+	ai_walk, 8,	makron_step_right,
+	ai_walk, 6,	NULL,
+	ai_walk, 12,	NULL,
+	ai_walk, 9,	NULL,
+	ai_walk, 6,	NULL,
+	ai_walk, 12,	NULL
+};
+mmove_t	makron_move_walk = {FRAME_walk204, FRAME_walk213, makron_frames_run, NULL};
+
+void makron_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &makron_move_walk;
+}
+
+void makron_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &makron_move_stand;
+	else
+		self->monsterinfo.currentmove = &makron_move_run;
+}
+
+mframe_t makron_frames_pain6 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	makron_popup,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,		// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	makron_taunt,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain6 = {FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run};
+
+mframe_t makron_frames_pain5 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain5 = {FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run};
+
+mframe_t makron_frames_pain4 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_pain4 = {FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run};
+
+mframe_t makron_frames_death2 [] =
+{
+	ai_move,	-15,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	-12,	NULL,
+	ai_move,	0,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 10
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	11,	NULL,
+	ai_move,	12,	NULL,
+	ai_move,	11,	makron_step_right,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 20
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 30
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	6,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	2,	NULL,			// 40
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,			// 50
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-6,	makron_step_right,
+	ai_move,	-4,	NULL,
+	ai_move,	-4,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 60
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	-5,	NULL,
+	ai_move,	-3,	makron_step_right,
+	ai_move,	-8,	NULL,
+	ai_move,	-3,	makron_step_left,
+	ai_move,	-7,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-4,	makron_step_right,			// 70
+	ai_move,	-6,	NULL,			
+	ai_move,	-7,	NULL,
+	ai_move,	0,	makron_step_left,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,			// 80
+	ai_move,	0,	NULL,			
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,			// 90
+	ai_move,	27,	makron_hit,			
+	ai_move,	26,	NULL,
+	ai_move,	0,	makron_brainsplorch,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL			// 95
+};
+mmove_t makron_move_death2 = {FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead};
+
+mframe_t makron_frames_death3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_death3 = {FRAME_death301, FRAME_death320, makron_frames_death3, NULL};
+
+mframe_t makron_frames_sight [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_sight= {FRAME_active01, FRAME_active13, makron_frames_sight, makron_run};
+
+void makronBFG (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_BFG], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+	gi.sound (self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0);
+	monster_fire_bfg (self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG);
+}	
+
+
+mframe_t makron_frames_attack3 []=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	makronBFG,		// FIXME: BFG Attack here
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack3 = {FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run};
+
+mframe_t makron_frames_attack4[]=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	MakronHyperblaster,		// fire
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack4 = {FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run};
+
+mframe_t makron_frames_attack5[]=
+{
+	ai_charge,	0,	makron_prerailgun,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	MakronSaveloc,
+	ai_move,	0,	MakronRailgun,		// Fire railgun
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t makron_move_attack5 = {FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run};
+
+void MakronSaveloc (edict_t *self)
+{
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+};
+
+// FIXME: He's not firing from the proper Z
+void MakronRailgun (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right, start);
+	
+	// calc direction to where we targted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1);
+}
+
+// FIXME: This is all wrong. He's not firing at the proper angles.
+void MakronHyperblaster (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	flash_number = MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, vec);
+		vectoangles (vec, vec);
+		dir[0] = vec[0];
+	}
+	else
+	{
+		dir[0] = 0;
+	}
+	if (self->s.frame <= FRAME_attak413)
+		dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413);
+	else
+		dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421);
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, NULL, NULL);
+
+	monster_fire_blaster (self, start, forward, 15, 1000, MZ2_MAKRON_BLASTER_1, EF_BLASTER);
+}	
+
+
+void makron_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames
+	if (damage <=25)
+		if (qrandom()<0.2)
+			return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+
+	if (damage <= 40)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE,0);
+		self->monsterinfo.currentmove = &makron_move_pain4;
+	}
+	else if (damage <= 110)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE,0);
+		self->monsterinfo.currentmove = &makron_move_pain5;
+	}
+	else
+	{
+		if (damage <= 150)
+			if (qrandom() <= 0.45)
+			{
+				gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0);
+				self->monsterinfo.currentmove = &makron_move_pain6;
+			}
+		else
+			if (qrandom() <= 0.35)
+			{
+				gi.sound (self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE,0);
+				self->monsterinfo.currentmove = &makron_move_pain6;
+			}
+	}
+};
+
+void makron_sight(edict_t *self, edict_t *)
+{
+	self->monsterinfo.currentmove = &makron_move_sight;
+};
+
+void makron_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	r;
+
+	r = qrandom();
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+
+	if (r <= 0.3)
+		self->monsterinfo.currentmove = &makron_move_attack3;
+	else if (r <= 0.6)
+		self->monsterinfo.currentmove = &makron_move_attack4;
+	else
+		self->monsterinfo.currentmove = &makron_move_attack5;
+}
+
+/*
+---
+Makron Torso. This needs to be spawned in
+---
+*/
+
+void makron_torso_think (edict_t *self)
+{
+	if (++self->s.frame < 365)
+		self->nextthink = level.time + FRAMETIME;
+	else
+	{		
+		self->s.frame = 346;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+void makron_torso (edict_t *ent)
+{
+	ent->movetype = MOVETYPE_NONE;
+	ent->solid = SOLID_NOT;
+	VectorSet (ent->mins, -8, -8, 0);
+	VectorSet (ent->maxs, 8, 8, 8);
+	ent->s.frame = 346;
+	ent->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	ent->think = makron_torso_think;
+	ent->nextthink = level.time + 2 * FRAMETIME;
+	ent->s.sound = gi.soundindex ("makron/spine.wav");
+	gi.linkentity (ent);
+}
+
+
+//
+// death
+//
+
+void makron_dead (edict_t *self)
+{
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void makron_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	edict_t *tempent;
+
+	int		n;
+
+	self->s.sound = 0;
+	// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 1 /*4*/; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	tempent = G_Spawn();
+	VectorCopy (self->s.origin, tempent->s.origin);
+	VectorCopy (self->s.angles, tempent->s.angles);
+	tempent->s.origin[1] -= 84;
+	makron_torso (tempent);
+
+	self->monsterinfo.currentmove = &makron_move_death2;
+	
+}
+
+qboolean Makron_CheckAttack (edict_t *self)
+{
+	vec3_t	spot1, spot2;
+	vec3_t	temp;
+	float	chance;
+	trace_t	tr;
+	int			enemy_range;
+	float		enemy_yaw;
+
+	if (self->enemy->health > 0)
+	{
+	// see if any entities are in the way of the shot
+		VectorCopy (self->s.origin, spot1);
+		spot1[2] += self->viewheight;
+		VectorCopy (self->enemy->s.origin, spot2);
+		spot2[2] += self->enemy->viewheight;
+
+		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
+
+		// do we have a clear shot?
+		if (tr.ent != self->enemy)
+			return false;
+	}
+	
+	enemy_range = range(self, self->enemy);
+	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
+	enemy_yaw = vectoyaw(temp);
+
+	self->ideal_yaw = enemy_yaw;
+
+
+	// melee attack
+	if (enemy_range == RANGE_MELEE)
+	{
+		if (self->monsterinfo.melee)
+			self->monsterinfo.attack_state = AS_MELEE;
+		else
+			self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+	
+// missile attack
+	if (!self->monsterinfo.attack)
+		return false;
+		
+	if (level.time < self->monsterinfo.attack_finished)
+		return false;
+		
+	if (enemy_range == RANGE_FAR)
+		return false;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MELEE)
+	{
+		chance = 0.8;
+	}
+	else if (enemy_range == RANGE_NEAR)
+	{
+		chance = 0.4;
+	}
+	else if (enemy_range == RANGE_MID)
+	{
+		chance = 0.2;
+	}
+	else
+	{
+		return false;
+	}
+
+	if (qrandom () < chance)
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		self->monsterinfo.attack_finished = level.time + 2*qrandom();
+		return true;
+	}
+
+	if (self->flags & FL_FLY)
+	{
+		if (qrandom() < 0.3)
+			self->monsterinfo.attack_state = AS_SLIDING;
+		else
+			self->monsterinfo.attack_state = AS_STRAIGHT;
+	}
+
+	return false;
+}
+
+
+//
+// monster_makron
+//
+
+void MakronPrecache (void)
+{
+	sound_pain4 = gi.soundindex ("makron/pain3.wav");
+	sound_pain5 = gi.soundindex ("makron/pain2.wav");
+	sound_pain6 = gi.soundindex ("makron/pain1.wav");
+	sound_death = gi.soundindex ("makron/death.wav");
+	sound_step_left = gi.soundindex ("makron/step1.wav");
+	sound_step_right = gi.soundindex ("makron/step2.wav");
+	sound_attack_bfg = gi.soundindex ("makron/bfg_fire.wav");
+	sound_brainsplorch = gi.soundindex ("makron/brain1.wav");
+	sound_prerailgun = gi.soundindex ("makron/rail_up.wav");
+	sound_popup = gi.soundindex ("makron/popup.wav");
+	sound_taunt1 = gi.soundindex ("makron/voice4.wav");
+	sound_taunt2 = gi.soundindex ("makron/voice3.wav");
+	sound_taunt3 = gi.soundindex ("makron/voice.wav");
+	sound_hit = gi.soundindex ("makron/bhit.wav");
+
+	gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+}
+
+/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_makron (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	MakronPrecache ();
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss3/rider/tris.md2");
+	VectorSet (self->mins, -30, -30, 0);
+	VectorSet (self->maxs, 30, 30, 90);
+
+	self->health = 3000;
+	self->gib_health = -2000;
+	self->mass = 500;
+
+	self->pain = makron_pain;
+	self->die = makron_die;
+	self->monsterinfo.stand = makron_stand;
+	self->monsterinfo.walk = makron_walk;
+	self->monsterinfo.run = makron_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = makron_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = makron_sight;
+	self->monsterinfo.checkattack = Makron_CheckAttack;
+
+	gi.linkentity (self);
+	
+//	self->monsterinfo.currentmove = &makron_move_stand;
+	self->monsterinfo.currentmove = &makron_move_sight;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+}
+
+
+/*
+=================
+MakronSpawn
+
+=================
+*/
+void MakronSpawn (edict_t *self)
+{
+	vec3_t		vec;
+	edict_t		*player;
+
+	SP_monster_makron (self);
+
+	// jump at player
+	player = level.sight_client;
+	if (!player)
+		return;
+
+	VectorSubtract (player->s.origin, self->s.origin, vec);
+	self->s.angles[YAW] = vectoyaw(vec);
+	VectorNormalize (vec);
+	VectorMA (vec3_origin, 400, vec, self->velocity);
+	self->velocity[2] = 200;
+	self->groundentity = NULL;
+}
+
+/*
+=================
+MakronToss
+
+Jorg is just about dead, so set up to launch Makron out
+=================
+*/
+void MakronToss (edict_t *self)
+{
+	edict_t	*ent;
+
+	ent = G_Spawn ();
+	ent->nextthink = level.time + 0.8;
+	ent->think = MakronSpawn;
+	ent->target = self->target;
+	VectorCopy (self->s.origin, ent->s.origin);
+}
--- /dev/null
+++ b/xatrix/m_boss32.h
@@ -1,0 +1,497 @@
+// G:\quake2\baseq2\models/monsters/boss3/rider
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak201        	18
+#define FRAME_attak202        	19
+#define FRAME_attak203        	20
+#define FRAME_attak204        	21
+#define FRAME_attak205        	22
+#define FRAME_attak206        	23
+#define FRAME_attak207        	24
+#define FRAME_attak208        	25
+#define FRAME_attak209        	26
+#define FRAME_attak210        	27
+#define FRAME_attak211        	28
+#define FRAME_attak212        	29
+#define FRAME_attak213        	30
+#define FRAME_death01         	31
+#define FRAME_death02         	32
+#define FRAME_death03         	33
+#define FRAME_death04         	34
+#define FRAME_death05         	35
+#define FRAME_death06         	36
+#define FRAME_death07         	37
+#define FRAME_death08         	38
+#define FRAME_death09         	39
+#define FRAME_death10         	40
+#define FRAME_death11         	41
+#define FRAME_death12         	42
+#define FRAME_death13         	43
+#define FRAME_death14         	44
+#define FRAME_death15         	45
+#define FRAME_death16         	46
+#define FRAME_death17         	47
+#define FRAME_death18         	48
+#define FRAME_death19         	49
+#define FRAME_death20         	50
+#define FRAME_death21         	51
+#define FRAME_death22         	52
+#define FRAME_death23         	53
+#define FRAME_death24         	54
+#define FRAME_death25         	55
+#define FRAME_death26         	56
+#define FRAME_death27         	57
+#define FRAME_death28         	58
+#define FRAME_death29         	59
+#define FRAME_death30         	60
+#define FRAME_death31         	61
+#define FRAME_death32         	62
+#define FRAME_death33         	63
+#define FRAME_death34         	64
+#define FRAME_death35         	65
+#define FRAME_death36         	66
+#define FRAME_death37         	67
+#define FRAME_death38         	68
+#define FRAME_death39         	69
+#define FRAME_death40         	70
+#define FRAME_death41         	71
+#define FRAME_death42         	72
+#define FRAME_death43         	73
+#define FRAME_death44         	74
+#define FRAME_death45         	75
+#define FRAME_death46         	76
+#define FRAME_death47         	77
+#define FRAME_death48         	78
+#define FRAME_death49         	79
+#define FRAME_death50         	80
+#define FRAME_pain101         	81
+#define FRAME_pain102         	82
+#define FRAME_pain103         	83
+#define FRAME_pain201         	84
+#define FRAME_pain202         	85
+#define FRAME_pain203         	86
+#define FRAME_pain301         	87
+#define FRAME_pain302         	88
+#define FRAME_pain303         	89
+#define FRAME_pain304         	90
+#define FRAME_pain305         	91
+#define FRAME_pain306         	92
+#define FRAME_pain307         	93
+#define FRAME_pain308         	94
+#define FRAME_pain309         	95
+#define FRAME_pain310         	96
+#define FRAME_pain311         	97
+#define FRAME_pain312         	98
+#define FRAME_pain313         	99
+#define FRAME_pain314         	100
+#define FRAME_pain315         	101
+#define FRAME_pain316         	102
+#define FRAME_pain317         	103
+#define FRAME_pain318         	104
+#define FRAME_pain319         	105
+#define FRAME_pain320         	106
+#define FRAME_pain321         	107
+#define FRAME_pain322         	108
+#define FRAME_pain323         	109
+#define FRAME_pain324         	110
+#define FRAME_pain325         	111
+#define FRAME_stand01         	112
+#define FRAME_stand02         	113
+#define FRAME_stand03         	114
+#define FRAME_stand04         	115
+#define FRAME_stand05         	116
+#define FRAME_stand06         	117
+#define FRAME_stand07         	118
+#define FRAME_stand08         	119
+#define FRAME_stand09         	120
+#define FRAME_stand10         	121
+#define FRAME_stand11         	122
+#define FRAME_stand12         	123
+#define FRAME_stand13         	124
+#define FRAME_stand14         	125
+#define FRAME_stand15         	126
+#define FRAME_stand16         	127
+#define FRAME_stand17         	128
+#define FRAME_stand18         	129
+#define FRAME_stand19         	130
+#define FRAME_stand20         	131
+#define FRAME_stand21         	132
+#define FRAME_stand22         	133
+#define FRAME_stand23         	134
+#define FRAME_stand24         	135
+#define FRAME_stand25         	136
+#define FRAME_stand26         	137
+#define FRAME_stand27         	138
+#define FRAME_stand28         	139
+#define FRAME_stand29         	140
+#define FRAME_stand30         	141
+#define FRAME_stand31         	142
+#define FRAME_stand32         	143
+#define FRAME_stand33         	144
+#define FRAME_stand34         	145
+#define FRAME_stand35         	146
+#define FRAME_stand36         	147
+#define FRAME_stand37         	148
+#define FRAME_stand38         	149
+#define FRAME_stand39         	150
+#define FRAME_stand40         	151
+#define FRAME_stand41         	152
+#define FRAME_stand42         	153
+#define FRAME_stand43         	154
+#define FRAME_stand44         	155
+#define FRAME_stand45         	156
+#define FRAME_stand46         	157
+#define FRAME_stand47         	158
+#define FRAME_stand48         	159
+#define FRAME_stand49         	160
+#define FRAME_stand50         	161
+#define FRAME_stand51         	162
+#define FRAME_walk01          	163
+#define FRAME_walk02          	164
+#define FRAME_walk03          	165
+#define FRAME_walk04          	166
+#define FRAME_walk05          	167
+#define FRAME_walk06          	168
+#define FRAME_walk07          	169
+#define FRAME_walk08          	170
+#define FRAME_walk09          	171
+#define FRAME_walk10          	172
+#define FRAME_walk11          	173
+#define FRAME_walk12          	174
+#define FRAME_walk13          	175
+#define FRAME_walk14          	176
+#define FRAME_walk15          	177
+#define FRAME_walk16          	178
+#define FRAME_walk17          	179
+#define FRAME_walk18          	180
+#define FRAME_walk19          	181
+#define FRAME_walk20          	182
+#define FRAME_walk21          	183
+#define FRAME_walk22          	184
+#define FRAME_walk23          	185
+#define FRAME_walk24          	186
+#define FRAME_walk25          	187
+#define FRAME_active01        	188
+#define FRAME_active02        	189
+#define FRAME_active03        	190
+#define FRAME_active04        	191
+#define FRAME_active05        	192
+#define FRAME_active06        	193
+#define FRAME_active07        	194
+#define FRAME_active08        	195
+#define FRAME_active09        	196
+#define FRAME_active10        	197
+#define FRAME_active11        	198
+#define FRAME_active12        	199
+#define FRAME_active13        	200
+#define FRAME_attak301        	201
+#define FRAME_attak302        	202
+#define FRAME_attak303        	203
+#define FRAME_attak304        	204
+#define FRAME_attak305        	205
+#define FRAME_attak306        	206
+#define FRAME_attak307        	207
+#define FRAME_attak308        	208
+#define FRAME_attak401        	209
+#define FRAME_attak402        	210
+#define FRAME_attak403        	211
+#define FRAME_attak404        	212
+#define FRAME_attak405        	213
+#define FRAME_attak406        	214
+#define FRAME_attak407        	215
+#define FRAME_attak408        	216
+#define FRAME_attak409        	217
+#define FRAME_attak410        	218
+#define FRAME_attak411        	219
+#define FRAME_attak412        	220
+#define FRAME_attak413        	221
+#define FRAME_attak414        	222
+#define FRAME_attak415        	223
+#define FRAME_attak416        	224
+#define FRAME_attak417        	225
+#define FRAME_attak418        	226
+#define FRAME_attak419        	227
+#define FRAME_attak420        	228
+#define FRAME_attak421        	229
+#define FRAME_attak422        	230
+#define FRAME_attak423        	231
+#define FRAME_attak424        	232
+#define FRAME_attak425        	233
+#define FRAME_attak426        	234
+#define FRAME_attak501        	235
+#define FRAME_attak502        	236
+#define FRAME_attak503        	237
+#define FRAME_attak504        	238
+#define FRAME_attak505        	239
+#define FRAME_attak506        	240
+#define FRAME_attak507        	241
+#define FRAME_attak508        	242
+#define FRAME_attak509        	243
+#define FRAME_attak510        	244
+#define FRAME_attak511        	245
+#define FRAME_attak512        	246
+#define FRAME_attak513        	247
+#define FRAME_attak514        	248
+#define FRAME_attak515        	249
+#define FRAME_attak516        	250
+#define FRAME_death201        	251
+#define FRAME_death202        	252
+#define FRAME_death203        	253
+#define FRAME_death204        	254
+#define FRAME_death205        	255
+#define FRAME_death206        	256
+#define FRAME_death207        	257
+#define FRAME_death208        	258
+#define FRAME_death209        	259
+#define FRAME_death210        	260
+#define FRAME_death211        	261
+#define FRAME_death212        	262
+#define FRAME_death213        	263
+#define FRAME_death214        	264
+#define FRAME_death215        	265
+#define FRAME_death216        	266
+#define FRAME_death217        	267
+#define FRAME_death218        	268
+#define FRAME_death219        	269
+#define FRAME_death220        	270
+#define FRAME_death221        	271
+#define FRAME_death222        	272
+#define FRAME_death223        	273
+#define FRAME_death224        	274
+#define FRAME_death225        	275
+#define FRAME_death226        	276
+#define FRAME_death227        	277
+#define FRAME_death228        	278
+#define FRAME_death229        	279
+#define FRAME_death230        	280
+#define FRAME_death231        	281
+#define FRAME_death232        	282
+#define FRAME_death233        	283
+#define FRAME_death234        	284
+#define FRAME_death235        	285
+#define FRAME_death236        	286
+#define FRAME_death237        	287
+#define FRAME_death238        	288
+#define FRAME_death239        	289
+#define FRAME_death240        	290
+#define FRAME_death241        	291
+#define FRAME_death242        	292
+#define FRAME_death243        	293
+#define FRAME_death244        	294
+#define FRAME_death245        	295
+#define FRAME_death246        	296
+#define FRAME_death247        	297
+#define FRAME_death248        	298
+#define FRAME_death249        	299
+#define FRAME_death250        	300
+#define FRAME_death251        	301
+#define FRAME_death252        	302
+#define FRAME_death253        	303
+#define FRAME_death254        	304
+#define FRAME_death255        	305
+#define FRAME_death256        	306
+#define FRAME_death257        	307
+#define FRAME_death258        	308
+#define FRAME_death259        	309
+#define FRAME_death260        	310
+#define FRAME_death261        	311
+#define FRAME_death262        	312
+#define FRAME_death263        	313
+#define FRAME_death264        	314
+#define FRAME_death265        	315
+#define FRAME_death266        	316
+#define FRAME_death267        	317
+#define FRAME_death268        	318
+#define FRAME_death269        	319
+#define FRAME_death270        	320
+#define FRAME_death271        	321
+#define FRAME_death272        	322
+#define FRAME_death273        	323
+#define FRAME_death274        	324
+#define FRAME_death275        	325
+#define FRAME_death276        	326
+#define FRAME_death277        	327
+#define FRAME_death278        	328
+#define FRAME_death279        	329
+#define FRAME_death280        	330
+#define FRAME_death281        	331
+#define FRAME_death282        	332
+#define FRAME_death283        	333
+#define FRAME_death284        	334
+#define FRAME_death285        	335
+#define FRAME_death286        	336
+#define FRAME_death287        	337
+#define FRAME_death288        	338
+#define FRAME_death289        	339
+#define FRAME_death290        	340
+#define FRAME_death291        	341
+#define FRAME_death292        	342
+#define FRAME_death293        	343
+#define FRAME_death294        	344
+#define FRAME_death295        	345
+#define FRAME_death301        	346
+#define FRAME_death302        	347
+#define FRAME_death303        	348
+#define FRAME_death304        	349
+#define FRAME_death305        	350
+#define FRAME_death306        	351
+#define FRAME_death307        	352
+#define FRAME_death308        	353
+#define FRAME_death309        	354
+#define FRAME_death310        	355
+#define FRAME_death311        	356
+#define FRAME_death312        	357
+#define FRAME_death313        	358
+#define FRAME_death314        	359
+#define FRAME_death315        	360
+#define FRAME_death316        	361
+#define FRAME_death317        	362
+#define FRAME_death318        	363
+#define FRAME_death319        	364
+#define FRAME_death320        	365
+#define FRAME_jump01          	366
+#define FRAME_jump02          	367
+#define FRAME_jump03          	368
+#define FRAME_jump04          	369
+#define FRAME_jump05          	370
+#define FRAME_jump06          	371
+#define FRAME_jump07          	372
+#define FRAME_jump08          	373
+#define FRAME_jump09          	374
+#define FRAME_jump10          	375
+#define FRAME_jump11          	376
+#define FRAME_jump12          	377
+#define FRAME_jump13          	378
+#define FRAME_pain401         	379
+#define FRAME_pain402         	380
+#define FRAME_pain403         	381
+#define FRAME_pain404         	382
+#define FRAME_pain501         	383
+#define FRAME_pain502         	384
+#define FRAME_pain503         	385
+#define FRAME_pain504         	386
+#define FRAME_pain601         	387
+#define FRAME_pain602         	388
+#define FRAME_pain603         	389
+#define FRAME_pain604         	390
+#define FRAME_pain605         	391
+#define FRAME_pain606         	392
+#define FRAME_pain607         	393
+#define FRAME_pain608         	394
+#define FRAME_pain609         	395
+#define FRAME_pain610         	396
+#define FRAME_pain611         	397
+#define FRAME_pain612         	398
+#define FRAME_pain613         	399
+#define FRAME_pain614         	400
+#define FRAME_pain615         	401
+#define FRAME_pain616         	402
+#define FRAME_pain617         	403
+#define FRAME_pain618         	404
+#define FRAME_pain619         	405
+#define FRAME_pain620         	406
+#define FRAME_pain621         	407
+#define FRAME_pain622         	408
+#define FRAME_pain623         	409
+#define FRAME_pain624         	410
+#define FRAME_pain625         	411
+#define FRAME_pain626         	412
+#define FRAME_pain627         	413
+#define FRAME_stand201        	414
+#define FRAME_stand202        	415
+#define FRAME_stand203        	416
+#define FRAME_stand204        	417
+#define FRAME_stand205        	418
+#define FRAME_stand206        	419
+#define FRAME_stand207        	420
+#define FRAME_stand208        	421
+#define FRAME_stand209        	422
+#define FRAME_stand210        	423
+#define FRAME_stand211        	424
+#define FRAME_stand212        	425
+#define FRAME_stand213        	426
+#define FRAME_stand214        	427
+#define FRAME_stand215        	428
+#define FRAME_stand216        	429
+#define FRAME_stand217        	430
+#define FRAME_stand218        	431
+#define FRAME_stand219        	432
+#define FRAME_stand220        	433
+#define FRAME_stand221        	434
+#define FRAME_stand222        	435
+#define FRAME_stand223        	436
+#define FRAME_stand224        	437
+#define FRAME_stand225        	438
+#define FRAME_stand226        	439
+#define FRAME_stand227        	440
+#define FRAME_stand228        	441
+#define FRAME_stand229        	442
+#define FRAME_stand230        	443
+#define FRAME_stand231        	444
+#define FRAME_stand232        	445
+#define FRAME_stand233        	446
+#define FRAME_stand234        	447
+#define FRAME_stand235        	448
+#define FRAME_stand236        	449
+#define FRAME_stand237        	450
+#define FRAME_stand238        	451
+#define FRAME_stand239        	452
+#define FRAME_stand240        	453
+#define FRAME_stand241        	454
+#define FRAME_stand242        	455
+#define FRAME_stand243        	456
+#define FRAME_stand244        	457
+#define FRAME_stand245        	458
+#define FRAME_stand246        	459
+#define FRAME_stand247        	460
+#define FRAME_stand248        	461
+#define FRAME_stand249        	462
+#define FRAME_stand250        	463
+#define FRAME_stand251        	464
+#define FRAME_stand252        	465
+#define FRAME_stand253        	466
+#define FRAME_stand254        	467
+#define FRAME_stand255        	468
+#define FRAME_stand256        	469
+#define FRAME_stand257        	470
+#define FRAME_stand258        	471
+#define FRAME_stand259        	472
+#define FRAME_stand260        	473
+#define FRAME_walk201         	474
+#define FRAME_walk202         	475
+#define FRAME_walk203         	476
+#define FRAME_walk204         	477
+#define FRAME_walk205         	478
+#define FRAME_walk206         	479
+#define FRAME_walk207         	480
+#define FRAME_walk208         	481
+#define FRAME_walk209         	482
+#define FRAME_walk210         	483
+#define FRAME_walk211         	484
+#define FRAME_walk212         	485
+#define FRAME_walk213         	486
+#define FRAME_walk214         	487
+#define FRAME_walk215         	488
+#define FRAME_walk216         	489
+#define FRAME_walk217         	490
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_boss5.c
@@ -1,0 +1,701 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_supertank.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_search2;
+
+static	int	tread_sound;
+
+void BossExplode2 (edict_t *self);
+
+void TreadSound2 (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void boss5_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+}
+
+
+void boss5_dead (edict_t *self);
+void boss5Rocket (edict_t *self);
+void boss5MachineGun (edict_t *self);
+void boss5_reattack1(edict_t *self);
+
+
+//
+// stand
+//
+
+mframe_t boss5_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	boss5_move_stand = {FRAME_stand_1, FRAME_stand_60, boss5_frames_stand, NULL};
+	
+void boss5_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &boss5_move_stand;
+}
+
+
+mframe_t boss5_frames_run [] =
+{
+	ai_run, 12,	TreadSound2,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL
+};
+mmove_t	boss5_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, boss5_frames_run, NULL};
+
+//
+// walk
+//
+
+
+mframe_t boss5_frames_forward [] =
+{
+	ai_walk, 4,	TreadSound2,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL
+};
+mmove_t	boss5_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, boss5_frames_forward, NULL};
+
+void boss5_forward (edict_t *self)
+{
+		self->monsterinfo.currentmove = &boss5_move_forward;
+}
+
+void boss5_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &boss5_move_forward;
+}
+
+void boss5_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &boss5_move_stand;
+	else
+		self->monsterinfo.currentmove = &boss5_move_run;
+}
+
+mframe_t boss5_frames_turn_right [] =
+{
+	ai_move,	0,	TreadSound2,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_turn_right = {FRAME_right_1, FRAME_right_18, boss5_frames_turn_right, boss5_run};
+
+mframe_t boss5_frames_turn_left [] =
+{
+	ai_move,	0,	TreadSound2,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_turn_left = {FRAME_left_1, FRAME_left_18, boss5_frames_turn_left, boss5_run};
+
+
+mframe_t boss5_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, boss5_frames_pain3, boss5_run};
+
+mframe_t boss5_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, boss5_frames_pain2, boss5_run};
+
+mframe_t boss5_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, boss5_frames_pain1, boss5_run};
+
+mframe_t boss5_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode2
+};
+mmove_t boss5_move_death = {FRAME_death_1, FRAME_death_24, boss5_frames_death1, boss5_dead};
+
+mframe_t boss5_frames_backward[] =
+{
+	ai_walk, 0,	TreadSound2,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL
+};
+mmove_t	boss5_move_backward = {FRAME_backwd_1, FRAME_backwd_18, boss5_frames_backward, NULL};
+
+mframe_t boss5_frames_attack4[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, boss5_frames_attack4, boss5_run};
+
+mframe_t boss5_frames_attack3[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, boss5_frames_attack3, boss5_run};
+
+mframe_t boss5_frames_attack2[]=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	boss5Rocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	boss5Rocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	boss5Rocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, boss5_frames_attack2, boss5_run};
+
+mframe_t boss5_frames_attack1[]=
+{
+	ai_charge,	0,	boss5MachineGun,
+	ai_charge,	0,	boss5MachineGun,
+	ai_charge,	0,	boss5MachineGun,
+	ai_charge,	0,	boss5MachineGun,
+	ai_charge,	0,	boss5MachineGun,
+	ai_charge,	0,	boss5MachineGun,
+
+};
+mmove_t boss5_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, boss5_frames_attack1, boss5_reattack1};
+
+mframe_t boss5_frames_end_attack1[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t boss5_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, boss5_frames_end_attack1, boss5_run};
+
+
+void boss5_reattack1(edict_t *self)
+{
+	if (visible(self, self->enemy))
+		if (qrandom() < 0.9)
+			self->monsterinfo.currentmove = &boss5_move_attack1;
+		else
+			self->monsterinfo.currentmove = &boss5_move_end_attack1;	
+	else
+		self->monsterinfo.currentmove = &boss5_move_end_attack1;
+}
+
+void boss5_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames
+	if (damage <=25)
+		if (qrandom()<0.2)
+			return;
+
+	// Don't go into pain if he's firing his rockets
+	if (skill->value >= 2)
+		if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) )
+			return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (damage <= 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &boss5_move_pain1;
+	}
+	else if (damage <= 25)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &boss5_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &boss5_move_pain3;
+	}
+};
+
+
+void boss5Rocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak2_8)
+		flash_number = MZ2_SUPERTANK_ROCKET_1;
+	else if (self->s.frame == FRAME_attak2_11)
+		flash_number = MZ2_SUPERTANK_ROCKET_2;
+	else // (self->s.frame == FRAME_attak2_14)
+		flash_number = MZ2_SUPERTANK_ROCKET_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_rocket (self, start, dir, 50, 500, flash_number);
+	
+}	
+
+void boss5MachineGun (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1);
+
+	//FIXME!!!
+	dir[0] = 0;
+	dir[1] = self->s.angles[1];
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		VectorMA (vec, 0, self->enemy->velocity, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, forward);
+		VectorNormalize (forward);
+  }
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}	
+
+
+void boss5_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+	//float	r;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+
+	//r = random();
+
+	// Attack 1 == Chaingun
+	// Attack 2 == Rocket Launcher
+
+	if (range <= 160)
+	{
+		self->monsterinfo.currentmove = &boss5_move_attack1;
+	}
+	else
+	{	// fire rockets more often at distance
+		if (qrandom() < 0.3)
+			self->monsterinfo.currentmove = &boss5_move_attack1;
+		else
+			self->monsterinfo.currentmove = &boss5_move_attack2;
+	}
+}
+
+
+//
+// death
+//
+
+void boss5_dead (edict_t *self)
+{
+	/*
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	*/
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void BossExplode2 (edict_t *self)
+{
+	vec3_t	org;
+	int		n;
+
+	self->think = BossExplode2;
+	VectorCopy (self->s.origin, org);
+	org[2] += 24 + (rand()&15);
+	switch (self->count++)
+	{
+	case 0:
+		org[0] -= 24;
+		org[1] -= 24;
+		break;
+	case 1:
+		org[0] += 24;
+		org[1] += 24;
+		break;
+	case 2:
+		org[0] += 24;
+		org[1] -= 24;
+		break;
+	case 3:
+		org[0] -= 24;
+		org[1] += 24;
+		break;
+	case 4:
+		org[0] -= 48;
+		org[1] -= 48;
+		break;
+	case 5:
+		org[0] += 48;
+		org[1] += 48;
+		break;
+	case 6:
+		org[0] -= 48;
+		org[1] += 48;
+		break;
+	case 7:
+		org[0] += 48;
+		org[1] -= 48;
+		break;
+	case 8:
+		self->s.sound = 0;
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC);
+		for (n= 0; n < 8; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (org);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	self->nextthink = level.time + 0.1;
+}
+
+
+void boss5_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &boss5_move_death;
+}
+
+//
+// monster_boss5
+//
+
+/*QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight 
+*/
+void SP_monster_boss5 (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav");
+	sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav");
+	sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav");
+	sound_death = gi.soundindex ("bosstank/btkdeth1.wav");
+	sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav");
+	sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav");
+
+//	self->s.sound = gi.soundindex ("bosstank/btkengn1.wav");
+	tread_sound = gi.soundindex ("bosstank/btkengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss5/tris.md2");
+	VectorSet (self->mins, -64, -64, 0);
+	VectorSet (self->maxs, 64, 64, 112);
+
+	self->health = 1500;
+	self->gib_health = -500;
+	self->mass = 800;
+
+	self->pain = boss5_pain;
+	self->die = boss5_die;
+	self->monsterinfo.stand = boss5_stand;
+	self->monsterinfo.walk = boss5_walk;
+	self->monsterinfo.run = boss5_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = boss5_attack;
+	self->monsterinfo.search = boss5_search;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &boss5_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	// RAFAEL
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+	self->monsterinfo.power_armor_power = 400;
+	
+
+	walkmonster_start(self);
+}
--- /dev/null
+++ b/xatrix/m_brain.c
@@ -1,0 +1,889 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_brain.h"
+
+
+static int	sound_chest_open;
+static int	sound_tentacles_extend;
+static int	sound_tentacles_retract;
+static int	sound_death;
+static int	sound_idle1;
+static int	sound_idle2;
+static int	sound_idle3;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_melee1;
+static int	sound_melee2;
+static int	sound_melee3;
+
+
+void brain_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void brain_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+void brain_run (edict_t *self);
+void brain_dead (edict_t *self);
+
+
+//
+// STAND
+//
+
+mframe_t brain_frames_stand [] =
+{
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL
+};
+mmove_t brain_move_stand = {FRAME_stand01, FRAME_stand30, brain_frames_stand, NULL};
+
+void brain_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &brain_move_stand;
+}
+
+
+//
+// IDLE
+//
+
+mframe_t brain_frames_idle [] =
+{
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL
+};
+mmove_t brain_move_idle = {FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand};
+
+void brain_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0);
+	self->monsterinfo.currentmove = &brain_move_idle;
+}
+
+
+//
+// WALK
+//
+mframe_t brain_frames_walk1 [] =
+{
+	ai_walk,	7,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	9,	NULL,
+	ai_walk,	-4,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	2,	NULL
+};
+mmove_t brain_move_walk1 = {FRAME_walk101, FRAME_walk111, brain_frames_walk1, NULL};
+
+// walk2 is FUBAR, do not use
+/*
+void brain_walk2_cycle (edict_t *self)
+{
+	if (qrandom() > 0.1)
+		self->monsterinfo.nextframe = FRAME_walk220;
+}
+
+mframe_t brain_frames_walk2 [] =
+{
+	ai_walk,	3,	NULL,
+	ai_walk,	-2,	NULL,
+	ai_walk,	-4,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	12,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	0,	NULL,
+
+	ai_walk,	-2,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	10,	NULL,		// Cycle Start
+
+	ai_walk,	-1,	NULL,
+	ai_walk,	7,	NULL,
+	ai_walk,	0,	NULL,
+	ai_walk,	3,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	-3,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	0,	NULL,
+
+	ai_walk,	4,	brain_walk2_cycle,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-8,	NULL,		
+	ai_walk,	0,	NULL,
+	ai_walk,	1,	NULL,
+	ai_walk,	5,	NULL,
+	ai_walk,	2,	NULL,
+	ai_walk,	-1,	NULL,
+	ai_walk,	-5,	NULL
+};
+mmove_t brain_move_walk2 = {FRAME_walk201, FRAME_walk240, brain_frames_walk2, NULL};
+*/
+
+void brain_walk (edict_t *self)
+{
+//	if (random() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_walk1;
+//	else
+//		self->monsterinfo.currentmove = &brain_move_walk2;
+}
+
+
+
+mframe_t brain_frames_defense [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_defense = {FRAME_defens01, FRAME_defens08, brain_frames_defense, NULL};
+
+mframe_t brain_frames_pain3 [] =
+{
+	ai_move,	-2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-4,	NULL
+};
+mmove_t brain_move_pain3 = {FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run};
+
+mframe_t brain_frames_pain2 [] =
+{
+	ai_move,	-2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-2,	NULL
+};
+mmove_t brain_move_pain2 = {FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run};
+
+mframe_t brain_frames_pain1 [] =
+{
+	ai_move,	-6,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	-1,	NULL
+};
+mmove_t brain_move_pain1 = {FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run};
+
+
+//
+// DUCK
+//
+
+void brain_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	gi.linkentity (self);
+}
+
+void brain_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void brain_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+mframe_t brain_frames_duck [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	-2,	brain_duck_down,
+	ai_move,	17,	brain_duck_hold,
+	ai_move,	-3,	NULL,
+	ai_move,	-1,	brain_duck_up,
+	ai_move,	-5,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-6,	NULL
+};
+mmove_t brain_move_duck = {FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run};
+
+void brain_dodge (edict_t *self, edict_t *attacker, float eta)
+{
+	if (qrandom() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.pausetime = level.time + eta + 0.5;
+	self->monsterinfo.currentmove = &brain_move_duck;
+}
+
+
+mframe_t brain_frames_death2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	9,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_death2 = {FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead};
+
+mframe_t brain_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	9,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t brain_move_death1 = {FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead};
+
+
+//
+// MELEE
+//
+
+void brain_swing_right (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0);
+}
+
+void brain_hit_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 40))
+		gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+}
+
+void brain_swing_left (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0);
+}
+
+void brain_hit_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 40))
+		gi.sound (self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
+}
+
+mframe_t brain_frames_attack1 [] =
+{
+	ai_charge,	8,	NULL,
+	ai_charge,	3,	NULL,
+	ai_charge,	5,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	-3,	brain_swing_right,
+	ai_charge,	0,	NULL,
+	ai_charge,	-5,	NULL,
+	ai_charge,	-7,	brain_hit_right,
+	ai_charge,	0,	NULL,
+	ai_charge,	6,	brain_swing_left,
+	ai_charge,	1,	NULL,
+	ai_charge,	2,	brain_hit_left,
+	ai_charge,	-3,	NULL,
+	ai_charge,	6,	NULL,
+	ai_charge,	-1,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	-11,NULL
+};
+mmove_t brain_move_attack1 = {FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run};
+
+void brain_chest_open (edict_t *self)
+{
+	self->spawnflags &= ~65536;
+	self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+	gi.sound (self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
+}
+
+void brain_tentacle_attack (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), -600) && skill->value > 0)
+		self->spawnflags |= 65536;
+	gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+}
+
+void brain_chest_closed (edict_t *self)
+{
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	if (self->spawnflags & 65536)
+	{
+		self->spawnflags &= ~65536;
+		self->monsterinfo.currentmove = &brain_move_attack1;
+	}
+}
+
+mframe_t brain_frames_attack2 [] =
+{
+	ai_charge,	5,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	0,	brain_chest_open,
+	ai_charge,	0,	NULL,
+	ai_charge,	13,	brain_tentacle_attack,
+	ai_charge,	0,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	-9,	brain_chest_closed,
+	ai_charge,	0,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	3,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	-6,	NULL
+};
+mmove_t brain_move_attack2 = {FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run};
+
+void brain_melee(edict_t *self)
+{
+	if (qrandom() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_attack1;
+	else
+		self->monsterinfo.currentmove = &brain_move_attack2;
+}
+
+static qboolean brain_tounge_attack_ok (vec3_t start, vec3_t end)
+{
+	vec3_t	dir, angles;
+
+	// check for max distance
+	VectorSubtract (start, end, dir);
+	if (VectorLength(dir) > 512)
+		return false;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 30)
+		return false;
+
+	return true;
+}
+
+void brain_tounge_attack (edict_t *self)
+{
+	vec3_t	offset, start, f, r, end, dir;
+	trace_t	tr;
+	int damage;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	// VectorSet (offset, 24, 0, 6);
+	VectorSet (offset, 24, 0, 16);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	if (!brain_tounge_attack_ok(start, end))
+	{
+		end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+		if (!brain_tounge_attack_ok(start, end))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+			if (!brain_tounge_attack_ok(start, end))
+				return;
+		}
+	}
+	VectorCopy (self->enemy->s.origin, end);
+
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if (tr.ent != self->enemy)
+		return;
+
+	damage = 5;
+	gi.sound (self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PARASITE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	VectorSubtract (start, end, dir);
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE);
+
+	// pull the enemy in
+	{
+		vec3_t	forward;
+		self->s.origin[2] += 1;
+		AngleVectors (self->s.angles, forward, NULL, NULL);
+		VectorScale (forward, -1200, self->enemy->velocity);
+	}
+	
+}
+
+// Brian right eye center
+
+struct r_eyeball 
+{
+	float x;
+	float y;
+	float z;
+} brain_reye [11] = {
+		{  0.746700, 0.238370, 34.167690 },
+        {  -1.076390, 0.238370, 33.386372 },
+        {  -1.335500, 5.334300, 32.177170 },
+        {  -0.175360, 8.846370, 30.635479 },
+        {  -2.757590, 7.804610, 30.150860 },
+        {  -5.575090, 5.152840, 30.056160 },
+        {  -7.017550, 3.262470, 30.552521 },
+        {  -7.915740, 0.638800, 33.176189 },
+        {  -3.915390, 8.285730, 33.976349 },
+        {  -0.913540, 10.933030, 34.141811 },
+        {  -0.369900, 8.923900, 34.189079 }
+};
+
+
+
+// Brain left eye center
+struct l_eyeball
+{
+	float x;
+	float y;
+	float z;
+} brain_leye [11] = {
+        {  -3.364710, 0.327750, 33.938381 },
+        {  -5.140450, 0.493480, 32.659851 },
+        {  -5.341980, 5.646980, 31.277901 },
+        {  -4.134480, 9.277440, 29.925621 },
+        {  -6.598340, 6.815090, 29.322620 },
+        {  -8.610840, 2.529650, 29.251591 },
+        {  -9.231360, 0.093280, 29.747959 },
+        {  -11.004110, 1.936930, 32.395260 },
+        {  -7.878310, 7.648190, 33.148151 },
+        {  -4.947370, 11.430050, 33.313610 },
+        {  -4.332820, 9.444570, 33.526340 }
+};
+
+// note to self
+// need to get an x,y,z offset for
+// each frame of the run cycle
+void brain_laserbeam (edict_t *self)
+{
+ 
+	vec3_t forward, right, up;
+	vec3_t tempang, start;
+	vec3_t	dir, angles, end;
+	edict_t *ent;
+
+	// RAFAEL
+	// cant call sound this frequent
+	if (qrandom() > 0.8)
+		gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0);
+
+	// check for max distance
+	
+	VectorCopy (self->s.origin, start);
+	VectorCopy (self->enemy->s.origin, end);
+	VectorSubtract (end, start, dir);
+	vectoangles (dir, angles);
+	
+	// dis is my right eye
+	ent = G_Spawn ();
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (angles, tempang);
+	AngleVectors (tempang, forward, right, up);
+	VectorCopy (tempang, ent->s.angles);
+	VectorCopy (ent->s.origin, start);
+	VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].x, right, start);
+	VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].y, forward, start);
+	VectorMA (start, brain_reye[self->s.frame - FRAME_walk101].z, up, start);
+	VectorCopy (start, ent->s.origin);
+	ent->enemy = self->enemy;
+	ent->owner = self;
+	ent->dmg = 1;
+	monster_dabeam (ent);
+   
+	// dis is me left eye
+	ent = G_Spawn ();  
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (angles, tempang);
+	AngleVectors (tempang, forward, right, up);
+	VectorCopy (tempang, ent->s.angles);
+	VectorCopy (ent->s.origin, start);
+	VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].x, right, start);
+	VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].y, forward, start);
+	VectorMA (start, brain_leye[self->s.frame - FRAME_walk101].z, up, start);
+	VectorCopy (start, ent->s.origin);
+	ent->enemy = self->enemy;
+	ent->owner = self;
+	ent->dmg = 1;
+	monster_dabeam (ent);
+	
+}
+
+void brain_laserbeam_reattack (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		if (visible (self, self->enemy))
+			if (self->enemy->health > 0)
+				self->s.frame = FRAME_walk101;
+}
+
+
+mframe_t brain_frames_attack3 [] =
+{
+	ai_charge,	5,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-4,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	0,	brain_chest_open,
+	ai_charge,	0,	brain_tounge_attack,
+	ai_charge,	13,	NULL,
+	ai_charge,	0,	brain_tentacle_attack,
+	ai_charge,	2,	NULL,
+	ai_charge,	0,	brain_tounge_attack,
+	ai_charge,	-9,	brain_chest_closed,
+	ai_charge,	0,	NULL,
+	ai_charge,	4,	NULL,
+	ai_charge,	3,	NULL,
+	ai_charge,	2,	NULL,
+	ai_charge,	-3,	NULL,
+	ai_charge,	-6,	NULL
+};
+mmove_t brain_move_attack3 = {FRAME_attak201, FRAME_attak217, brain_frames_attack3, brain_run};
+
+mframe_t brain_frames_attack4 [] =
+{
+	ai_charge,	9,	brain_laserbeam,
+	ai_charge,	2,	brain_laserbeam,
+	ai_charge,	3,	brain_laserbeam,
+	ai_charge,	3,	brain_laserbeam,
+	ai_charge,	1,	brain_laserbeam,
+	ai_charge,	0,	brain_laserbeam,
+	ai_charge,	0,	brain_laserbeam,
+	ai_charge,	10,	brain_laserbeam,
+	ai_charge,	-4,	brain_laserbeam,
+	ai_charge,	-1,	brain_laserbeam,
+	ai_charge,	2,	brain_laserbeam_reattack
+};
+mmove_t brain_move_attack4 = {FRAME_walk101, FRAME_walk111, brain_frames_attack4, brain_run};
+
+
+// RAFAEL
+void brain_attack (edict_t *self)
+{
+	int r;
+	
+	if (qrandom() < 0.8)
+	{
+		r = range (self, self->enemy);
+		if (r == RANGE_NEAR)
+		{
+			if (qrandom() < 0.5)
+				self->monsterinfo.currentmove = &brain_move_attack3;
+			else 
+				self->monsterinfo.currentmove = &brain_move_attack4;
+		}
+		else if (r > RANGE_NEAR)
+			self->monsterinfo.currentmove = &brain_move_attack4;
+	}
+	
+}
+
+//
+// RUN
+//
+
+mframe_t brain_frames_run [] =
+{
+	ai_run,	9,	NULL,
+	ai_run,	2,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	3,	NULL,
+	ai_run,	1,	NULL,
+	ai_run,	0,	NULL,
+	ai_run,	0,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	-4,	NULL,
+	ai_run,	-1,	NULL,
+	ai_run,	2,	NULL
+};
+mmove_t brain_move_run = {FRAME_walk101, FRAME_walk111, brain_frames_run, NULL};
+
+void brain_run (edict_t *self)
+{
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &brain_move_stand;
+	else
+		self->monsterinfo.currentmove = &brain_move_run;
+}
+
+
+void brain_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	r = qrandom();
+	if (r < 0.33)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain1;
+	}
+	else if (r < 0.66)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &brain_move_pain3;
+	}
+}
+
+void brain_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+
+void brain_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	self->s.effects = 0;
+	self->monsterinfo.power_armor_type = POWER_ARMOR_NONE;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	if (qrandom() <= 0.5)
+		self->monsterinfo.currentmove = &brain_move_death1;
+	else
+		self->monsterinfo.currentmove = &brain_move_death2;
+}
+
+/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_brain (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_chest_open = gi.soundindex ("brain/brnatck1.wav");
+	sound_tentacles_extend = gi.soundindex ("brain/brnatck2.wav");
+	sound_tentacles_retract = gi.soundindex ("brain/brnatck3.wav");
+	sound_death = gi.soundindex ("brain/brndeth1.wav");
+	sound_idle1 = gi.soundindex ("brain/brnidle1.wav");
+	sound_idle2 = gi.soundindex ("brain/brnidle2.wav");
+	sound_idle3 = gi.soundindex ("brain/brnlens1.wav");
+	sound_pain1 = gi.soundindex ("brain/brnpain1.wav");
+	sound_pain2 = gi.soundindex ("brain/brnpain2.wav");
+	sound_sight = gi.soundindex ("brain/brnsght1.wav");
+	sound_search = gi.soundindex ("brain/brnsrch1.wav");
+	sound_melee1 = gi.soundindex ("brain/melee1.wav");
+	sound_melee2 = gi.soundindex ("brain/melee2.wav");
+	sound_melee3 = gi.soundindex ("brain/melee3.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/brain/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 300;
+	self->gib_health = -150;
+	self->mass = 400;
+
+	self->pain = brain_pain;
+	self->die = brain_die;
+
+	self->monsterinfo.stand = brain_stand;
+	self->monsterinfo.walk = brain_walk;
+	self->monsterinfo.run = brain_run;
+	self->monsterinfo.dodge = brain_dodge;
+	self->monsterinfo.attack = brain_attack;
+	self->monsterinfo.melee = brain_melee;
+	self->monsterinfo.sight = brain_sight;
+	self->monsterinfo.search = brain_search;
+	self->monsterinfo.idle = brain_idle;
+
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SCREEN;
+	self->monsterinfo.power_armor_power = 100;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &brain_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_brain.h
@@ -1,0 +1,228 @@
+// G:\quake2\baseq2\models/monsters/brain
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_walk101         	0
+#define FRAME_walk102         	1
+#define FRAME_walk103         	2
+#define FRAME_walk104         	3
+#define FRAME_walk105         	4
+#define FRAME_walk106         	5
+#define FRAME_walk107         	6
+#define FRAME_walk108         	7
+#define FRAME_walk109         	8
+#define FRAME_walk110         	9
+#define FRAME_walk111         	10
+#define FRAME_walk112         	11
+#define FRAME_walk113         	12
+#define FRAME_walk201         	13
+#define FRAME_walk202         	14
+#define FRAME_walk203         	15
+#define FRAME_walk204         	16
+#define FRAME_walk205         	17
+#define FRAME_walk206         	18
+#define FRAME_walk207         	19
+#define FRAME_walk208         	20
+#define FRAME_walk209         	21
+#define FRAME_walk210         	22
+#define FRAME_walk211         	23
+#define FRAME_walk212         	24
+#define FRAME_walk213         	25
+#define FRAME_walk214         	26
+#define FRAME_walk215         	27
+#define FRAME_walk216         	28
+#define FRAME_walk217         	29
+#define FRAME_walk218         	30
+#define FRAME_walk219         	31
+#define FRAME_walk220         	32
+#define FRAME_walk221         	33
+#define FRAME_walk222         	34
+#define FRAME_walk223         	35
+#define FRAME_walk224         	36
+#define FRAME_walk225         	37
+#define FRAME_walk226         	38
+#define FRAME_walk227         	39
+#define FRAME_walk228         	40
+#define FRAME_walk229         	41
+#define FRAME_walk230         	42
+#define FRAME_walk231         	43
+#define FRAME_walk232         	44
+#define FRAME_walk233         	45
+#define FRAME_walk234         	46
+#define FRAME_walk235         	47
+#define FRAME_walk236         	48
+#define FRAME_walk237         	49
+#define FRAME_walk238         	50
+#define FRAME_walk239         	51
+#define FRAME_walk240         	52
+#define FRAME_attak101        	53
+#define FRAME_attak102        	54
+#define FRAME_attak103        	55
+#define FRAME_attak104        	56
+#define FRAME_attak105        	57
+#define FRAME_attak106        	58
+#define FRAME_attak107        	59
+#define FRAME_attak108        	60
+#define FRAME_attak109        	61
+#define FRAME_attak110        	62
+#define FRAME_attak111        	63
+#define FRAME_attak112        	64
+#define FRAME_attak113        	65
+#define FRAME_attak114        	66
+#define FRAME_attak115        	67
+#define FRAME_attak116        	68
+#define FRAME_attak117        	69
+#define FRAME_attak118        	70
+#define FRAME_attak201        	71
+#define FRAME_attak202        	72
+#define FRAME_attak203        	73
+#define FRAME_attak204        	74
+#define FRAME_attak205        	75
+#define FRAME_attak206        	76
+#define FRAME_attak207        	77
+#define FRAME_attak208        	78
+#define FRAME_attak209        	79
+#define FRAME_attak210        	80
+#define FRAME_attak211        	81
+#define FRAME_attak212        	82
+#define FRAME_attak213        	83
+#define FRAME_attak214        	84
+#define FRAME_attak215        	85
+#define FRAME_attak216        	86
+#define FRAME_attak217        	87
+#define FRAME_pain101         	88
+#define FRAME_pain102         	89
+#define FRAME_pain103         	90
+#define FRAME_pain104         	91
+#define FRAME_pain105         	92
+#define FRAME_pain106         	93
+#define FRAME_pain107         	94
+#define FRAME_pain108         	95
+#define FRAME_pain109         	96
+#define FRAME_pain110         	97
+#define FRAME_pain111         	98
+#define FRAME_pain112         	99
+#define FRAME_pain113         	100
+#define FRAME_pain114         	101
+#define FRAME_pain115         	102
+#define FRAME_pain116         	103
+#define FRAME_pain117         	104
+#define FRAME_pain118         	105
+#define FRAME_pain119         	106
+#define FRAME_pain120         	107
+#define FRAME_pain121         	108
+#define FRAME_pain201         	109
+#define FRAME_pain202         	110
+#define FRAME_pain203         	111
+#define FRAME_pain204         	112
+#define FRAME_pain205         	113
+#define FRAME_pain206         	114
+#define FRAME_pain207         	115
+#define FRAME_pain208         	116
+#define FRAME_pain301         	117
+#define FRAME_pain302         	118
+#define FRAME_pain303         	119
+#define FRAME_pain304         	120
+#define FRAME_pain305         	121
+#define FRAME_pain306         	122
+#define FRAME_death101        	123
+#define FRAME_death102        	124
+#define FRAME_death103        	125
+#define FRAME_death104        	126
+#define FRAME_death105        	127
+#define FRAME_death106        	128
+#define FRAME_death107        	129
+#define FRAME_death108        	130
+#define FRAME_death109        	131
+#define FRAME_death110        	132
+#define FRAME_death111        	133
+#define FRAME_death112        	134
+#define FRAME_death113        	135
+#define FRAME_death114        	136
+#define FRAME_death115        	137
+#define FRAME_death116        	138
+#define FRAME_death117        	139
+#define FRAME_death118        	140
+#define FRAME_death201        	141
+#define FRAME_death202        	142
+#define FRAME_death203        	143
+#define FRAME_death204        	144
+#define FRAME_death205        	145
+#define FRAME_duck01          	146
+#define FRAME_duck02          	147
+#define FRAME_duck03          	148
+#define FRAME_duck04          	149
+#define FRAME_duck05          	150
+#define FRAME_duck06          	151
+#define FRAME_duck07          	152
+#define FRAME_duck08          	153
+#define FRAME_defens01        	154
+#define FRAME_defens02        	155
+#define FRAME_defens03        	156
+#define FRAME_defens04        	157
+#define FRAME_defens05        	158
+#define FRAME_defens06        	159
+#define FRAME_defens07        	160
+#define FRAME_defens08        	161
+#define FRAME_stand01         	162
+#define FRAME_stand02         	163
+#define FRAME_stand03         	164
+#define FRAME_stand04         	165
+#define FRAME_stand05         	166
+#define FRAME_stand06         	167
+#define FRAME_stand07         	168
+#define FRAME_stand08         	169
+#define FRAME_stand09         	170
+#define FRAME_stand10         	171
+#define FRAME_stand11         	172
+#define FRAME_stand12         	173
+#define FRAME_stand13         	174
+#define FRAME_stand14         	175
+#define FRAME_stand15         	176
+#define FRAME_stand16         	177
+#define FRAME_stand17         	178
+#define FRAME_stand18         	179
+#define FRAME_stand19         	180
+#define FRAME_stand20         	181
+#define FRAME_stand21         	182
+#define FRAME_stand22         	183
+#define FRAME_stand23         	184
+#define FRAME_stand24         	185
+#define FRAME_stand25         	186
+#define FRAME_stand26         	187
+#define FRAME_stand27         	188
+#define FRAME_stand28         	189
+#define FRAME_stand29         	190
+#define FRAME_stand30         	191
+#define FRAME_stand31         	192
+#define FRAME_stand32         	193
+#define FRAME_stand33         	194
+#define FRAME_stand34         	195
+#define FRAME_stand35         	196
+#define FRAME_stand36         	197
+#define FRAME_stand37         	198
+#define FRAME_stand38         	199
+#define FRAME_stand39         	200
+#define FRAME_stand40         	201
+#define FRAME_stand41         	202
+#define FRAME_stand42         	203
+#define FRAME_stand43         	204
+#define FRAME_stand44         	205
+#define FRAME_stand45         	206
+#define FRAME_stand46         	207
+#define FRAME_stand47         	208
+#define FRAME_stand48         	209
+#define FRAME_stand49         	210
+#define FRAME_stand50         	211
+#define FRAME_stand51         	212
+#define FRAME_stand52         	213
+#define FRAME_stand53         	214
+#define FRAME_stand54         	215
+#define FRAME_stand55         	216
+#define FRAME_stand56         	217
+#define FRAME_stand57         	218
+#define FRAME_stand58         	219
+#define FRAME_stand59         	220
+#define FRAME_stand60         	221
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_chick.c
@@ -1,0 +1,665 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_chick.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+void chick_stand (edict_t *self);
+void chick_run (edict_t *self);
+void chick_reslash(edict_t *self);
+void chick_rerocket(edict_t *self);
+void chick_attack1(edict_t *self);
+
+static int	sound_missile_prelaunch;
+static int	sound_missile_launch;
+static int	sound_melee_swing;
+static int	sound_melee_hit;
+static int	sound_missile_reload;
+static int	sound_death1;
+static int	sound_death2;
+static int	sound_fall_down;
+static int	sound_idle1;
+static int	sound_idle2;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_sight;
+static int	sound_search;
+
+
+void ChickMoan (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0);
+}
+
+mframe_t chick_frames_fidget [] =
+{
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  ChickMoan,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL
+};
+mmove_t chick_move_fidget = {FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand};
+
+void chick_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() <= 0.3)
+		self->monsterinfo.currentmove = &chick_move_fidget;
+}
+
+mframe_t chick_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, chick_fidget,
+
+};
+mmove_t chick_move_stand = {FRAME_stand101, FRAME_stand130, chick_frames_stand, NULL};
+
+void chick_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_stand;
+}
+
+mframe_t chick_frames_start_run [] =
+{
+	ai_run, 1,  NULL,
+	ai_run, 0,  NULL,
+	ai_run, 0,	 NULL,
+	ai_run, -1, NULL, 
+	ai_run, -1, NULL, 
+	ai_run, 0,  NULL,
+	ai_run, 1,  NULL,
+	ai_run, 3,  NULL,
+	ai_run, 6,	 NULL,
+	ai_run, 3,	 NULL
+};
+mmove_t chick_move_start_run = {FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run};
+
+mframe_t chick_frames_run [] =
+{
+	ai_run, 6,	NULL,
+	ai_run, 8,  NULL,
+	ai_run, 13, NULL,
+	ai_run, 5,  NULL,
+	ai_run, 7,  NULL,
+	ai_run, 4,  NULL,
+	ai_run, 11, NULL,
+	ai_run, 5,  NULL,
+	ai_run, 9,  NULL,
+	ai_run, 7,  NULL
+
+};
+
+mmove_t chick_move_run = {FRAME_walk11, FRAME_walk20, chick_frames_run, NULL};
+
+mframe_t chick_frames_walk [] =
+{
+	ai_walk, 6,	 NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 13, NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 11, NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 9,  NULL,
+	ai_walk, 7,  NULL
+};
+
+mmove_t chick_move_walk = {FRAME_walk11, FRAME_walk20, chick_frames_walk, NULL};
+
+void chick_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_walk;
+}
+
+void chick_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &chick_move_stand;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &chick_move_walk ||
+		self->monsterinfo.currentmove == &chick_move_start_run)
+	{
+		self->monsterinfo.currentmove = &chick_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &chick_move_start_run;
+	}
+}
+
+mframe_t chick_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t chick_move_pain1 = {FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run};
+
+mframe_t chick_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t chick_move_pain2 = {FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run};
+
+mframe_t chick_frames_pain3 [] =
+{
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, -6,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, 11,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, 4,		NULL,
+	ai_move, 1,		NULL,
+	ai_move, 0,		NULL,
+	ai_move, -3,	NULL,
+	ai_move, -4,	NULL,
+	ai_move, 5,		NULL,
+	ai_move, 7,		NULL,
+	ai_move, -2,	NULL,
+	ai_move, 3,		NULL,
+	ai_move, -5,	NULL,
+	ai_move, -2,	NULL,
+	ai_move, -8,	NULL,
+	ai_move, 2,		NULL
+};
+mmove_t chick_move_pain3 = {FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run};
+
+void chick_pain (edict_t *self, edict_t *, float, int damage)
+{
+	float	r;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	r = qrandom();
+	if (r < 0.33)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else if (r < 0.66)
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
+
+	if (damage <= 10)
+		self->monsterinfo.currentmove = &chick_move_pain1;
+	else if (damage <= 25)
+		self->monsterinfo.currentmove = &chick_move_pain2;
+	else
+		self->monsterinfo.currentmove = &chick_move_pain3;
+}
+
+void chick_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 16);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;	
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t chick_frames_death2 [] =
+{
+	ai_move, -6, NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -5, NULL,
+	ai_move, 0, NULL,
+	ai_move, -1,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 10, NULL,
+	ai_move, 2,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 2, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -5, NULL,
+	ai_move, 4, NULL,
+	ai_move, 15, NULL,
+	ai_move, 14, NULL,
+	ai_move, 1, NULL
+};
+mmove_t chick_move_death2 = {FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead};
+
+mframe_t chick_frames_death1 [] =
+{
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -7, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 11, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL
+	
+};
+mmove_t chick_move_death1 = {FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead};
+
+void chick_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 2;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &chick_move_death1;
+		gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &chick_move_death2;
+		gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+	}
+}
+
+
+void chick_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void chick_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void chick_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+mframe_t chick_frames_duck [] =
+{
+	ai_move, 0, chick_duck_down,
+	ai_move, 1, NULL,
+	ai_move, 4, chick_duck_hold,
+	ai_move, -4,  NULL,
+	ai_move, -5,  chick_duck_up,
+	ai_move, 3, NULL,
+	ai_move, 1,  NULL
+};
+mmove_t chick_move_duck = {FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run};
+
+void chick_dodge (edict_t *self, edict_t *attacker, float)
+{
+	if (qrandom() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &chick_move_duck;
+}
+
+void ChickSlash (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 10);
+	gi.sound (self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0);
+	fire_hit (self, aim, (10 + (rand() %6)), 100);
+}
+
+
+void ChickRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+
+	if (self->s.skinnum > 1)
+		monster_fire_heat (self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
+	else
+		monster_fire_rocket (self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1);
+
+}	
+
+void Chick_PreAttack1 (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0);
+}
+
+void ChickReload (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t chick_frames_start_attack1 [] =
+{
+	ai_charge, 0,	Chick_PreAttack1,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 4,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -3,  NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 7,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	chick_attack1
+};
+mmove_t chick_move_start_attack1 = {FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, NULL};
+
+
+mframe_t chick_frames_attack1 [] =
+{
+	ai_charge, 19,	ChickRocket,
+	ai_charge, -6,	NULL,
+	ai_charge, -5,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -7,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 10,	ChickReload,
+	ai_charge, 4,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 4,	NULL,
+	ai_charge, 3,	chick_rerocket
+
+};
+mmove_t chick_move_attack1 = {FRAME_attak114, FRAME_attak127, chick_frames_attack1, NULL};
+
+mframe_t chick_frames_end_attack1 [] =
+{
+	ai_charge, -3,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -6,	NULL,
+	ai_charge, -4,	NULL,
+	ai_charge, -2,  NULL
+};
+mmove_t chick_move_end_attack1 = {FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run};
+
+void chick_rerocket(edict_t *self)
+{
+	if (self->enemy->health > 0)
+	{
+		if (range (self, self->enemy) > RANGE_MELEE)
+			if ( visible (self, self->enemy) )
+				if (qrandom() <= 0.6)
+				{
+					self->monsterinfo.currentmove = &chick_move_attack1;
+					return;
+				}
+	}	
+	self->monsterinfo.currentmove = &chick_move_end_attack1;
+}
+
+void chick_attack1(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_attack1;
+}
+
+mframe_t chick_frames_slash [] =
+{
+	ai_charge, 1,	NULL,
+	ai_charge, 7,	ChickSlash,
+	ai_charge, -7,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, -2,	chick_reslash
+};
+mmove_t chick_move_slash = {FRAME_attak204, FRAME_attak212, chick_frames_slash, NULL};
+
+mframe_t chick_frames_end_slash [] =
+{
+	ai_charge, -6,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -6,	NULL,
+	ai_charge, 0,	NULL
+};
+mmove_t chick_move_end_slash = {FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run};
+
+
+void chick_reslash(edict_t *self)
+{
+	if (self->enemy->health > 0)
+	{
+		if (range (self, self->enemy) == RANGE_MELEE)
+			if (qrandom() <= 0.9)
+			{				
+				self->monsterinfo.currentmove = &chick_move_slash;
+				return;
+			}
+			else
+			{
+				self->monsterinfo.currentmove = &chick_move_end_slash;
+				return;
+			}
+	}
+	self->monsterinfo.currentmove = &chick_move_end_slash;
+}
+
+void chick_slash(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_slash;
+}
+
+
+mframe_t chick_frames_start_slash [] =
+{	
+	ai_charge, 1,	NULL,
+	ai_charge, 8,	NULL,
+	ai_charge, 3,	NULL
+};
+mmove_t chick_move_start_slash = {FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash};
+
+
+
+void chick_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_start_slash;
+}
+
+
+void chick_attack(edict_t *self)
+{
+	self->monsterinfo.currentmove = &chick_move_start_attack1;
+}
+
+void chick_sight(edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+
+/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight 
+*/
+void SP_monster_chick (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_missile_prelaunch	= gi.soundindex ("chick/chkatck1.wav");	
+	sound_missile_launch	= gi.soundindex ("chick/chkatck2.wav");	
+	sound_melee_swing		= gi.soundindex ("chick/chkatck3.wav");	
+	sound_melee_hit			= gi.soundindex ("chick/chkatck4.wav");	
+	sound_missile_reload	= gi.soundindex ("chick/chkatck5.wav");	
+	sound_death1			= gi.soundindex ("chick/chkdeth1.wav");	
+	sound_death2			= gi.soundindex ("chick/chkdeth2.wav");	
+	sound_fall_down			= gi.soundindex ("chick/chkfall1.wav");	
+	sound_idle1				= gi.soundindex ("chick/chkidle1.wav");	
+	sound_idle2				= gi.soundindex ("chick/chkidle2.wav");	
+	sound_pain1				= gi.soundindex ("chick/chkpain1.wav");	
+	sound_pain2				= gi.soundindex ("chick/chkpain2.wav");	
+	sound_pain3				= gi.soundindex ("chick/chkpain3.wav");	
+	sound_sight				= gi.soundindex ("chick/chksght1.wav");	
+	sound_search			= gi.soundindex ("chick/chksrch1.wav");	
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2");
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 56);
+
+	self->health = 175;
+	self->gib_health = -70;
+	self->mass = 200;
+
+	self->pain = chick_pain;
+	self->die = chick_die;
+
+	self->monsterinfo.stand = chick_stand;
+	self->monsterinfo.walk = chick_walk;
+	self->monsterinfo.run = chick_run;
+	self->monsterinfo.dodge = chick_dodge;
+	self->monsterinfo.attack = chick_attack;
+	self->monsterinfo.melee = chick_melee;
+	self->monsterinfo.sight = chick_sight;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &chick_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
+
+
+/*QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight 
+*/
+void SP_monster_chick_heat (edict_t *self)
+{
+	SP_monster_chick (self);
+	self->s.skinnum = 3;
+}
--- /dev/null
+++ b/xatrix/m_chick.h
@@ -1,0 +1,294 @@
+// G:\quake2\baseq2\models/monsters/bitch
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak113        	12
+#define FRAME_attak114        	13
+#define FRAME_attak115        	14
+#define FRAME_attak116        	15
+#define FRAME_attak117        	16
+#define FRAME_attak118        	17
+#define FRAME_attak119        	18
+#define FRAME_attak120        	19
+#define FRAME_attak121        	20
+#define FRAME_attak122        	21
+#define FRAME_attak123        	22
+#define FRAME_attak124        	23
+#define FRAME_attak125        	24
+#define FRAME_attak126        	25
+#define FRAME_attak127        	26
+#define FRAME_attak128        	27
+#define FRAME_attak129        	28
+#define FRAME_attak130        	29
+#define FRAME_attak131        	30
+#define FRAME_attak132        	31
+#define FRAME_attak201        	32
+#define FRAME_attak202        	33
+#define FRAME_attak203        	34
+#define FRAME_attak204        	35
+#define FRAME_attak205        	36
+#define FRAME_attak206        	37
+#define FRAME_attak207        	38
+#define FRAME_attak208        	39
+#define FRAME_attak209        	40
+#define FRAME_attak210        	41
+#define FRAME_attak211        	42
+#define FRAME_attak212        	43
+#define FRAME_attak213        	44
+#define FRAME_attak214        	45
+#define FRAME_attak215        	46
+#define FRAME_attak216        	47
+#define FRAME_death101        	48
+#define FRAME_death102        	49
+#define FRAME_death103        	50
+#define FRAME_death104        	51
+#define FRAME_death105        	52
+#define FRAME_death106        	53
+#define FRAME_death107        	54
+#define FRAME_death108        	55
+#define FRAME_death109        	56
+#define FRAME_death110        	57
+#define FRAME_death111        	58
+#define FRAME_death112        	59
+#define FRAME_death201        	60
+#define FRAME_death202        	61
+#define FRAME_death203        	62
+#define FRAME_death204        	63
+#define FRAME_death205        	64
+#define FRAME_death206        	65
+#define FRAME_death207        	66
+#define FRAME_death208        	67
+#define FRAME_death209        	68
+#define FRAME_death210        	69
+#define FRAME_death211        	70
+#define FRAME_death212        	71
+#define FRAME_death213        	72
+#define FRAME_death214        	73
+#define FRAME_death215        	74
+#define FRAME_death216        	75
+#define FRAME_death217        	76
+#define FRAME_death218        	77
+#define FRAME_death219        	78
+#define FRAME_death220        	79
+#define FRAME_death221        	80
+#define FRAME_death222        	81
+#define FRAME_death223        	82
+#define FRAME_duck01          	83
+#define FRAME_duck02          	84
+#define FRAME_duck03          	85
+#define FRAME_duck04          	86
+#define FRAME_duck05          	87
+#define FRAME_duck06          	88
+#define FRAME_duck07          	89
+#define FRAME_pain101         	90
+#define FRAME_pain102         	91
+#define FRAME_pain103         	92
+#define FRAME_pain104         	93
+#define FRAME_pain105         	94
+#define FRAME_pain201         	95
+#define FRAME_pain202         	96
+#define FRAME_pain203         	97
+#define FRAME_pain204         	98
+#define FRAME_pain205         	99
+#define FRAME_pain301         	100
+#define FRAME_pain302         	101
+#define FRAME_pain303         	102
+#define FRAME_pain304         	103
+#define FRAME_pain305         	104
+#define FRAME_pain306         	105
+#define FRAME_pain307         	106
+#define FRAME_pain308         	107
+#define FRAME_pain309         	108
+#define FRAME_pain310         	109
+#define FRAME_pain311         	110
+#define FRAME_pain312         	111
+#define FRAME_pain313         	112
+#define FRAME_pain314         	113
+#define FRAME_pain315         	114
+#define FRAME_pain316         	115
+#define FRAME_pain317         	116
+#define FRAME_pain318         	117
+#define FRAME_pain319         	118
+#define FRAME_pain320         	119
+#define FRAME_pain321         	120
+#define FRAME_stand101        	121
+#define FRAME_stand102        	122
+#define FRAME_stand103        	123
+#define FRAME_stand104        	124
+#define FRAME_stand105        	125
+#define FRAME_stand106        	126
+#define FRAME_stand107        	127
+#define FRAME_stand108        	128
+#define FRAME_stand109        	129
+#define FRAME_stand110        	130
+#define FRAME_stand111        	131
+#define FRAME_stand112        	132
+#define FRAME_stand113        	133
+#define FRAME_stand114        	134
+#define FRAME_stand115        	135
+#define FRAME_stand116        	136
+#define FRAME_stand117        	137
+#define FRAME_stand118        	138
+#define FRAME_stand119        	139
+#define FRAME_stand120        	140
+#define FRAME_stand121        	141
+#define FRAME_stand122        	142
+#define FRAME_stand123        	143
+#define FRAME_stand124        	144
+#define FRAME_stand125        	145
+#define FRAME_stand126        	146
+#define FRAME_stand127        	147
+#define FRAME_stand128        	148
+#define FRAME_stand129        	149
+#define FRAME_stand130        	150
+#define FRAME_stand201        	151
+#define FRAME_stand202        	152
+#define FRAME_stand203        	153
+#define FRAME_stand204        	154
+#define FRAME_stand205        	155
+#define FRAME_stand206        	156
+#define FRAME_stand207        	157
+#define FRAME_stand208        	158
+#define FRAME_stand209        	159
+#define FRAME_stand210        	160
+#define FRAME_stand211        	161
+#define FRAME_stand212        	162
+#define FRAME_stand213        	163
+#define FRAME_stand214        	164
+#define FRAME_stand215        	165
+#define FRAME_stand216        	166
+#define FRAME_stand217        	167
+#define FRAME_stand218        	168
+#define FRAME_stand219        	169
+#define FRAME_stand220        	170
+#define FRAME_stand221        	171
+#define FRAME_stand222        	172
+#define FRAME_stand223        	173
+#define FRAME_stand224        	174
+#define FRAME_stand225        	175
+#define FRAME_stand226        	176
+#define FRAME_stand227        	177
+#define FRAME_stand228        	178
+#define FRAME_stand229        	179
+#define FRAME_stand230        	180
+#define FRAME_walk01          	181
+#define FRAME_walk02          	182
+#define FRAME_walk03          	183
+#define FRAME_walk04          	184
+#define FRAME_walk05          	185
+#define FRAME_walk06          	186
+#define FRAME_walk07          	187
+#define FRAME_walk08          	188
+#define FRAME_walk09          	189
+#define FRAME_walk10          	190
+#define FRAME_walk11          	191
+#define FRAME_walk12          	192
+#define FRAME_walk13          	193
+#define FRAME_walk14          	194
+#define FRAME_walk15          	195
+#define FRAME_walk16          	196
+#define FRAME_walk17          	197
+#define FRAME_walk18          	198
+#define FRAME_walk19          	199
+#define FRAME_walk20          	200
+#define FRAME_walk21          	201
+#define FRAME_walk22          	202
+#define FRAME_walk23          	203
+#define FRAME_walk24          	204
+#define FRAME_walk25          	205
+#define FRAME_walk26          	206
+#define FRAME_walk27          	207
+#define FRAME_recln201        	208
+#define FRAME_recln202        	209
+#define FRAME_recln203        	210
+#define FRAME_recln204        	211
+#define FRAME_recln205        	212
+#define FRAME_recln206        	213
+#define FRAME_recln207        	214
+#define FRAME_recln208        	215
+#define FRAME_recln209        	216
+#define FRAME_recln210        	217
+#define FRAME_recln211        	218
+#define FRAME_recln212        	219
+#define FRAME_recln213        	220
+#define FRAME_recln214        	221
+#define FRAME_recln215        	222
+#define FRAME_recln216        	223
+#define FRAME_recln217        	224
+#define FRAME_recln218        	225
+#define FRAME_recln219        	226
+#define FRAME_recln220        	227
+#define FRAME_recln221        	228
+#define FRAME_recln222        	229
+#define FRAME_recln223        	230
+#define FRAME_recln224        	231
+#define FRAME_recln225        	232
+#define FRAME_recln226        	233
+#define FRAME_recln227        	234
+#define FRAME_recln228        	235
+#define FRAME_recln229        	236
+#define FRAME_recln230        	237
+#define FRAME_recln231        	238
+#define FRAME_recln232        	239
+#define FRAME_recln233        	240
+#define FRAME_recln234        	241
+#define FRAME_recln235        	242
+#define FRAME_recln236        	243
+#define FRAME_recln237        	244
+#define FRAME_recln238        	245
+#define FRAME_recln239        	246
+#define FRAME_recln240        	247
+#define FRAME_recln101        	248
+#define FRAME_recln102        	249
+#define FRAME_recln103        	250
+#define FRAME_recln104        	251
+#define FRAME_recln105        	252
+#define FRAME_recln106        	253
+#define FRAME_recln107        	254
+#define FRAME_recln108        	255
+#define FRAME_recln109        	256
+#define FRAME_recln110        	257
+#define FRAME_recln111        	258
+#define FRAME_recln112        	259
+#define FRAME_recln113        	260
+#define FRAME_recln114        	261
+#define FRAME_recln115        	262
+#define FRAME_recln116        	263
+#define FRAME_recln117        	264
+#define FRAME_recln118        	265
+#define FRAME_recln119        	266
+#define FRAME_recln120        	267
+#define FRAME_recln121        	268
+#define FRAME_recln122        	269
+#define FRAME_recln123        	270
+#define FRAME_recln124        	271
+#define FRAME_recln125        	272
+#define FRAME_recln126        	273
+#define FRAME_recln127        	274
+#define FRAME_recln128        	275
+#define FRAME_recln129        	276
+#define FRAME_recln130        	277
+#define FRAME_recln131        	278
+#define FRAME_recln132        	279
+#define FRAME_recln133        	280
+#define FRAME_recln134        	281
+#define FRAME_recln135        	282
+#define FRAME_recln136        	283
+#define FRAME_recln137        	284
+#define FRAME_recln138        	285
+#define FRAME_recln139        	286
+#define FRAME_recln140        	287
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_fixbot.c
@@ -1,0 +1,1317 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_fixbot.h"
+
+
+#define MZ2_fixbot_BLASTER_1				MZ2_HOVER_BLASTER_1
+
+qboolean visible (edict_t *self, edict_t *other);
+qboolean infront (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_die;
+static int  sound_weld1;
+static int  sound_weld2;
+static int  sound_weld3;
+
+void fixbot_run (edict_t *self);
+void fixbot_stand (edict_t *self);
+void fixbot_dead (edict_t *self);
+void fixbot_attack (edict_t *self);
+void fixbot_fire_blaster (edict_t *self);
+void fixbot_fire_welder (edict_t *self);
+void fixbot_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+void use_scanner (edict_t *self);
+void change_to_roam (edict_t *self);
+void fly_vertical (edict_t *self);
+
+extern mmove_t fixbot_move_forward;
+extern mmove_t fixbot_move_stand;
+extern mmove_t fixbot_move_stand2;
+extern mmove_t fixbot_move_roamgoal;
+
+extern mmove_t fixbot_move_weld_start;
+extern mmove_t fixbot_move_weld;
+extern mmove_t fixbot_move_weld_end;
+extern mmove_t fixbot_move_takeoff;
+extern mmove_t fixbot_move_landing;
+extern mmove_t fixbot_move_turn;
+
+extern void roam_goal (edict_t *self);
+void ED_CallSpawn (edict_t *ent);
+
+edict_t *fixbot_FindDeadMonster (edict_t *self)
+{
+	edict_t	*ent = NULL;
+	edict_t	*best = NULL;
+
+	while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
+	{
+		if (ent == self)
+			continue;
+		if (!(ent->svflags & SVF_MONSTER))
+			continue;
+		if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+			continue;
+		if (ent->owner)
+			continue;
+		if (ent->health > 0)
+			continue;
+		if (ent->nextthink)
+			continue;
+		if (!visible(self, ent))
+			continue;
+		if (!best)
+		{
+			best = ent;
+			continue;
+		}
+		if (ent->max_health <= best->max_health)
+			continue;
+		best = ent;
+	}
+
+	return best;
+}
+
+
+int fixbot_search (edict_t *self)
+{
+	edict_t	*ent;
+
+	if (!self->goalentity)
+	{
+		ent = fixbot_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->owner = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+			return (1);
+		}
+	}
+	return (0);
+}
+
+void landing_goal (edict_t *self)
+{
+	trace_t tr;
+	vec3_t forward, right, up;
+	vec3_t end;
+	edict_t *ent;
+	
+	ent = G_Spawn ();	
+	ent->classname = "bot_goal";
+	ent->solid = SOLID_BBOX;
+	ent->owner = self;
+	gi.linkentity (ent);
+	
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, 24);
+	
+	AngleVectors (self->s.angles, forward, right, up);
+	VectorMA (self->s.origin, 32, forward, end);
+	VectorMA (self->s.origin, -8096, up, end);
+		
+	tr = gi.trace (self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID);
+
+	VectorCopy (tr.endpos, ent->s.origin);
+	
+	self->goalentity = self->enemy = ent;
+	self->monsterinfo.currentmove = &fixbot_move_landing;		
+	
+
+}
+
+
+void takeoff_goal (edict_t *self)
+{
+	trace_t tr;
+	vec3_t forward, right, up;
+	vec3_t end;
+	edict_t *ent;
+	
+	ent = G_Spawn ();	
+	ent->classname = "bot_goal";
+	ent->solid = SOLID_BBOX;
+	ent->owner = self;
+	gi.linkentity (ent);
+	
+	VectorSet (ent->mins, -32, -32, -24);
+	VectorSet (ent->maxs, 32, 32, 24);
+	
+		
+	AngleVectors (self->s.angles, forward, right, up);
+	VectorMA (self->s.origin, 32, forward, end);
+	VectorMA (self->s.origin, 128, up, end);
+		
+	tr = gi.trace (self->s.origin, ent->mins, ent->maxs, end, self, MASK_MONSTERSOLID);
+
+	VectorCopy (tr.endpos, ent->s.origin);
+	
+	self->goalentity = self->enemy = ent;
+	self->monsterinfo.currentmove = &fixbot_move_takeoff;		
+	
+
+}
+
+void change_to_roam (edict_t *self)
+{
+	
+	if (fixbot_search(self))
+		return;
+
+	self->monsterinfo.currentmove = &fixbot_move_roamgoal;
+
+	
+	if (self->spawnflags & 16)
+	{
+		landing_goal (self);
+		self->monsterinfo.currentmove = &fixbot_move_landing;
+		self->spawnflags &= ~16;
+		self->spawnflags = 32;
+	}
+	if (self->spawnflags & 8) 
+	{
+		takeoff_goal (self);
+		self->monsterinfo.currentmove = &fixbot_move_takeoff;
+		self->spawnflags &= ~8;
+		self->spawnflags = 32;
+	}
+	if (self->spawnflags & 4)
+	{
+		self->monsterinfo.currentmove = &fixbot_move_roamgoal;
+		self->spawnflags &= ~4;
+		self->spawnflags = 32;
+	}
+	if (!self->spawnflags)
+	{
+		self->monsterinfo.currentmove = &fixbot_move_stand2;
+	}
+
+}
+
+
+void roam_goal (edict_t *self)
+{
+
+	trace_t		tr;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	edict_t		*ent;
+	vec3_t		dang;
+	int			len, oldlen, i;
+	vec3_t		vec;
+	vec3_t		whichvec;
+		
+	ent = G_Spawn ();	
+	ent->classname = "bot_goal";
+	ent->solid = SOLID_BBOX;
+	ent->owner = self;
+	gi.linkentity (ent);
+
+	oldlen = 0;
+	for (i=0; i<12; i++) 
+	{
+		
+		VectorCopy (self->s.angles, dang);
+
+		if (i < 6)
+			dang[YAW] += 30 * i;
+		else 
+			dang[YAW] -= 30 * (i-6);
+
+		AngleVectors (dang, forward, right, up);
+		VectorMA (self->s.origin, 8192, forward, end);
+		
+		tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SHOT);
+
+		VectorSubtract (self->s.origin, tr.endpos, vec);
+		len = VectorNormalize (vec);
+		
+		if (len > oldlen)
+		{
+			oldlen=len;
+			VectorCopy (tr.endpos, whichvec);
+		}
+		
+	}
+	
+	VectorCopy (whichvec, ent->s.origin);
+	self->goalentity = self->enemy = ent;
+	
+	self->monsterinfo.currentmove = &fixbot_move_turn;		
+
+
+}
+
+void use_scanner (edict_t *self)
+{
+	edict_t *ent = NULL;
+	
+	float   radius = 1024;
+	vec3_t	vec;
+	
+	int len;
+
+	while ((ent = findradius(ent, self->s.origin, radius)) != NULL)
+	{
+		if (ent->health >= 100)
+		{
+			if (strcmp (ent->classname, "object_repair") == 0)
+			{
+				if (visible(self, ent))
+				{	
+					// remove the old one
+					if (strcmp (self->goalentity->classname, "bot_goal") == 0)
+					{
+						self->goalentity->nextthink = level.time + 0.1;
+						self->goalentity->think = G_FreeEdict;
+					}	
+					
+					self->goalentity = self->enemy = ent;
+					
+					VectorSubtract (self->s.origin, self->goalentity->s.origin, vec);
+					len = VectorNormalize (vec);
+
+					if (len < 32)
+					{
+						self->monsterinfo.currentmove = &fixbot_move_weld_start;
+						return;
+					}
+				return;
+				}
+			}
+		}
+	}
+
+	VectorSubtract (self->s.origin, self->goalentity->s.origin, vec);
+	len = VectorLength (vec);
+
+	if (len < 32)
+	{
+		if (strcmp (self->goalentity->classname, "object_repair") == 0)
+		{
+			self->monsterinfo.currentmove = &fixbot_move_weld_start;
+		}
+		else 
+		{
+			self->goalentity->nextthink = level.time + 0.1;
+			self->goalentity->think = G_FreeEdict;
+			self->goalentity = self->enemy = NULL;
+			self->monsterinfo.currentmove = &fixbot_move_stand;
+		}
+		return;
+	}
+
+	VectorSubtract (self->s.origin, self->s.old_origin, vec);
+	len = VectorLength (vec);
+
+	/* 
+	  bot is stuck get new goalentity
+	*/
+	if (len == 0)
+	{
+		if (strcmp (self->goalentity->classname , "object_repair") == 0)
+		{
+			self->monsterinfo.currentmove = &fixbot_move_stand;
+		}
+		else 
+		{
+			self->goalentity->nextthink = level.time + 0.1;
+			self->goalentity->think = G_FreeEdict;
+			self->goalentity = self->enemy = NULL;
+			self->monsterinfo.currentmove = &fixbot_move_stand;
+		}
+	}
+
+}
+
+
+/*
+	when the bot has found a landing pad
+	it will proceed to its goalentity
+	just above the landing pad and
+	decend translated along the z the current
+	frames are at 10fps
+*/ 
+void blastoff (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread)
+{
+	trace_t		tr;
+	vec3_t		dir;
+	vec3_t		forward, right, up;
+	vec3_t		end;
+	float		r;
+	float		u;
+	vec3_t		water_start;
+	qboolean	water = false;
+	int			content_mask = MASK_SHOT | MASK_WATER;
+
+	hspread+= (self->s.frame - FRAME_takeoff_01);
+	vspread+= (self->s.frame - FRAME_takeoff_01);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);
+	if (!(tr.fraction < 1.0))
+	{
+		vectoangles (aimdir, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*hspread;
+		u = crandom()*vspread;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		if (gi.pointcontents (start) & MASK_WATER)
+		{
+			water = true;
+			VectorCopy (start, water_start);
+			content_mask &= ~MASK_WATER;
+		}
+
+		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
+
+		// see if we hit water
+		if (tr.contents & MASK_WATER)
+		{
+			int		color;
+
+			water = true;
+			VectorCopy (tr.endpos, water_start);
+
+			if (!VectorCompare (start, tr.endpos))
+			{
+				if (tr.contents & CONTENTS_WATER)
+				{
+					if (strcmp(tr.surface->name, "*brwater") == 0)
+						color = SPLASH_BROWN_WATER;
+					else
+						color = SPLASH_BLUE_WATER;
+				}
+				else if (tr.contents & CONTENTS_SLIME)
+					color = SPLASH_SLIME;
+				else if (tr.contents & CONTENTS_LAVA)
+					color = SPLASH_LAVA;
+				else
+					color = SPLASH_UNKNOWN;
+
+				if (color != SPLASH_UNKNOWN)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (TE_SPLASH);
+					gi.WriteByte (8);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.WriteByte (color);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+				}
+
+				// change bullet's course when it enters water
+				VectorSubtract (end, start, dir);
+				vectoangles (dir, dir);
+				AngleVectors (dir, forward, right, up);
+				r = crandom()*hspread*2;
+				u = crandom()*vspread*2;
+				VectorMA (water_start, 8192, forward, end);
+				VectorMA (end, r, right, end);
+				VectorMA (end, u, up, end);
+			}
+
+			// re-trace ignoring water this time
+			tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT);
+		}
+	}
+
+	// send gun puff / flash
+	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
+	{
+		if (tr.fraction < 1.0)
+		{
+			if (tr.ent->takedamage)
+			{
+				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, MOD_BLASTOFF);
+			}
+			else
+			{
+				if (strncmp (tr.surface->name, "sky", 3) != 0)
+				{
+					gi.WriteByte (svc_temp_entity);
+					gi.WriteByte (te_impact);
+					gi.WritePosition (tr.endpos);
+					gi.WriteDir (tr.plane.normal);
+					gi.multicast (tr.endpos, MULTICAST_PVS);
+
+					if (self->client)
+						PlayerNoise(self, tr.endpos, PNOISE_IMPACT);
+				}
+			}
+		}
+	}
+
+	// if went through water, determine where the end and make a bubble trail
+	if (water)
+	{
+		vec3_t	pos;
+
+		VectorSubtract (tr.endpos, water_start, dir);
+		VectorNormalize (dir);
+		VectorMA (tr.endpos, -2, dir, pos);
+		if (gi.pointcontents (pos) & MASK_WATER)
+			VectorCopy (pos, tr.endpos);
+		else
+			tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER);
+
+		VectorAdd (water_start, tr.endpos, pos);
+		VectorScale (pos, 0.5, pos);
+
+		gi.WriteByte (svc_temp_entity);
+		gi.WriteByte (TE_BUBBLETRAIL);
+		gi.WritePosition (water_start);
+		gi.WritePosition (tr.endpos);
+		gi.multicast (pos, MULTICAST_PVS);
+	}
+}
+
+
+void fly_vertical (edict_t *self)
+{
+	int i;
+	vec3_t v;
+	vec3_t forward, right, up;
+	vec3_t start;
+	vec3_t tempvec;
+	
+	VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+	self->ideal_yaw = vectoyaw(v);	
+	M_ChangeYaw (self);
+	
+	if (self->s.frame == FRAME_landing_58 || self->s.frame == FRAME_takeoff_16)
+	{
+		self->goalentity->nextthink = level.time + 0.1;
+		self->goalentity->think = G_FreeEdict;
+		self->monsterinfo.currentmove = &fixbot_move_stand;
+		self->goalentity = self->enemy = NULL;
+	}
+
+	// kick up some particles
+	VectorCopy (self->s.angles, tempvec);
+	tempvec[PITCH] += 90;
+
+	AngleVectors (tempvec, forward, right, up);
+	VectorCopy (self->s.origin, start);
+	
+	for (i=0; i< 10; i++)
+		blastoff (self, start, forward, 2, 1, TE_SHOTGUN, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD);
+
+	// needs sound
+}
+
+
+void fly_vertical2 (edict_t *self)
+{
+	vec3_t v;
+	int len;
+	
+	VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+	len = VectorLength (v);
+	self->ideal_yaw = vectoyaw(v);	
+	M_ChangeYaw (self);
+	
+	if (len < 32)
+	{
+		self->goalentity->nextthink = level.time + 0.1;
+		self->goalentity->think = G_FreeEdict;
+		self->monsterinfo.currentmove = &fixbot_move_stand;
+		self->goalentity = self->enemy = NULL;
+	}
+		
+	// needs sound
+}
+
+mframe_t fixbot_frames_landing [] =
+{
+	ai_move, 0,  NULL,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2,
+	ai_move, 0, fly_vertical2
+};
+mmove_t fixbot_move_landing = { FRAME_landing_01, FRAME_landing_58, fixbot_frames_landing, NULL };
+
+/*
+	generic ambient stand
+*/
+mframe_t fixbot_frames_stand [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, change_to_roam
+	
+};
+mmove_t	fixbot_move_stand = {FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand, NULL};
+
+mframe_t fixbot_frames_stand2 [] =
+{
+	
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+	
+};
+mmove_t	fixbot_move_stand2 = {FRAME_ambient_01, FRAME_ambient_19, fixbot_frames_stand2, NULL};
+
+
+/*
+	will need the pickup offset for the front pincers
+	object will need to stop forward of the object 
+	and take the object with it ( this may require a variant of liftoff and landing )
+*/
+mframe_t fixbot_frames_pickup [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+	
+};
+mmove_t fixbot_move_pickup = { FRAME_pickup_01, FRAME_pickup_27, fixbot_frames_pickup, NULL};
+
+/*
+	generic frame to move bot
+*/
+mframe_t fixbot_frames_roamgoal [] =
+{
+	ai_move, 0, roam_goal
+};
+mmove_t fixbot_move_roamgoal = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_roamgoal, NULL};
+
+
+void ai_facing (edict_t *self, float)
+{
+	vec3_t v;
+
+	if (infront (self, self->goalentity))
+		self->monsterinfo.currentmove = &fixbot_move_forward;
+	else
+	{
+		VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+		self->ideal_yaw = vectoyaw(v);	
+		M_ChangeYaw (self);
+	}
+
+};
+
+mframe_t fixbot_frames_turn [] =
+{
+	ai_facing,	0,	NULL
+};
+mmove_t fixbot_move_turn = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_turn, NULL};
+
+
+void go_roam (edict_t *self)
+{
+	self->monsterinfo.currentmove = &fixbot_move_stand;	
+}
+
+
+/*
+	takeoff
+*/
+mframe_t fixbot_frames_takeoff [] =
+{
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical,
+	ai_move,	0.01,	fly_vertical
+};
+mmove_t fixbot_move_takeoff = {FRAME_takeoff_01, FRAME_takeoff_16, fixbot_frames_takeoff, NULL};
+
+
+/* findout what this is */
+mframe_t fixbot_frames_paina [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t fixbot_move_paina = {FRAME_paina_01, FRAME_paina_06, fixbot_frames_paina, fixbot_run};
+
+/* findout what this is */
+mframe_t fixbot_frames_painb [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t fixbot_move_painb = {FRAME_painb_01, FRAME_painb_08, fixbot_frames_painb, fixbot_run};
+
+/*
+	backup from pain
+	call a generic painsound
+	some spark effects
+*/
+mframe_t fixbot_frames_pain3 [] =
+{
+	ai_move,	-1,	NULL
+};
+mmove_t fixbot_move_pain3 = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_pain3, fixbot_run};
+
+/*
+	bot has compleated landing
+	and is now on the grownd
+	( may need second land if the bot is releasing jib into jib vat )
+*/
+mframe_t fixbot_frames_land [] =
+{
+	ai_move,	0,	NULL
+};
+mmove_t fixbot_move_land = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_land, NULL};
+
+
+void M_MoveToGoal (edict_t *ent, float dist);
+
+void ai_movetogoal (edict_t *self, float dist)
+{
+	M_MoveToGoal (self, dist);
+}
+/*
+	
+*/
+mframe_t fixbot_frames_forward [] =
+{
+	ai_movetogoal,	5,	use_scanner
+};
+mmove_t fixbot_move_forward = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_forward, NULL};
+
+
+/*
+	
+*/
+mframe_t fixbot_frames_walk [] =
+{
+	ai_walk,	5,	NULL
+};
+mmove_t fixbot_move_walk = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_walk, NULL};
+
+
+/*
+	
+*/
+mframe_t fixbot_frames_run [] =
+{
+	ai_run,	10,	NULL
+};
+mmove_t fixbot_move_run = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_run, NULL};
+
+/*
+	raf
+	note to self
+	they could have a timer that will cause
+	the bot to explode on countdown
+*/
+mframe_t fixbot_frames_death1 [] =
+{
+	ai_move,	0,	NULL
+};
+mmove_t fixbot_move_death1 = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_death1, fixbot_dead};
+
+//
+mframe_t fixbot_frames_backward [] =
+{
+	ai_move,	0,	NULL
+};
+mmove_t fixbot_move_backward = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_backward, NULL};
+
+//
+mframe_t fixbot_frames_start_attack [] =
+{
+	ai_charge,	0,	NULL
+};
+mmove_t fixbot_move_start_attack = {FRAME_freeze_01, FRAME_freeze_01, fixbot_frames_start_attack, fixbot_attack};
+
+/*
+	TBD: 
+	need to get laser attack anim
+	attack with the laser blast
+*/
+mframe_t fixbot_frames_attack1 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	-10,  fixbot_fire_blaster
+};
+mmove_t fixbot_move_attack1 = {FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_attack1, NULL};
+
+
+int check_telefrag (edict_t *self)
+{
+	vec3_t	start = { 0, 0, 0 };
+	vec3_t	forward, right, up;
+	trace_t	tr;
+
+	AngleVectors (self->enemy->s.angles, forward, right, up);
+	VectorMA (start, 48, up, start);
+	tr = gi.trace (self->enemy->s.origin, self->enemy->mins, self->enemy->maxs, start, self, MASK_MONSTERSOLID);
+	if (tr.ent->takedamage)
+	{
+		tr.ent->health = -1000;
+		return (0);
+	}
+			
+	return (1);
+}
+
+void fixbot_fire_laser (edict_t *self)
+{
+ 
+	vec3_t forward, right, up;
+	vec3_t tempang, start;
+	vec3_t	dir, angles, end;
+	edict_t *ent;
+
+	// critter dun got blown up while bein' fixed
+	if (self->enemy->health <= self->enemy->gib_health)
+	{
+		self->monsterinfo.currentmove = &fixbot_move_stand;
+		self->monsterinfo.aiflags &= ~AI_MEDIC;
+		return;
+	}
+
+	gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0);
+
+	VectorCopy (self->s.origin, start);
+	VectorCopy (self->enemy->s.origin, end);
+	VectorSubtract (end, start, dir);
+	vectoangles (dir, angles);
+	
+	ent = G_Spawn ();
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (angles, tempang);
+	AngleVectors (tempang, forward, right, up);
+	VectorCopy (tempang, ent->s.angles);
+	VectorCopy (ent->s.origin, start);
+	
+	VectorMA (start, 16, forward, start);
+	
+	VectorCopy (start, ent->s.origin);
+	ent->enemy = self->enemy;
+	ent->owner = self;
+	ent->dmg = -1;
+	monster_dabeam (ent);
+
+	if (self->enemy->health > (self->enemy->mass/10))
+	{
+		// sorry guys but had to fix the problem this way
+		// if it doesn't do this then two creatures can share the same space
+		// and its real bad.
+		if (check_telefrag (self)) 
+		{
+			self->enemy->spawnflags = 0;
+			self->enemy->monsterinfo.aiflags = 0;
+			self->enemy->target = NULL;
+			self->enemy->targetname = NULL;
+			self->enemy->combattarget = NULL;
+			self->enemy->deathtarget = NULL;
+			self->enemy->owner = self;
+			ED_CallSpawn (self->enemy);
+			self->enemy->owner = NULL;
+			self->s.origin[2] += 1;
+		
+			self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+
+			self->monsterinfo.currentmove = &fixbot_move_stand;
+			self->monsterinfo.aiflags &= ~AI_MEDIC;
+
+		}
+	}
+	else
+		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+}
+
+mframe_t fixbot_frames_laserattack [] =
+{
+	ai_charge,	0,	fixbot_fire_laser,
+	ai_charge,	0,	fixbot_fire_laser,
+	ai_charge,	0,	fixbot_fire_laser,
+	ai_charge,	0,	fixbot_fire_laser,
+	ai_charge,	0,	fixbot_fire_laser,
+	ai_charge,	0,  fixbot_fire_laser
+};
+mmove_t fixbot_move_laserattack = {FRAME_shoot_01, FRAME_shoot_06, fixbot_frames_laserattack, NULL};
+
+
+/*
+	need to get forward translation data
+	for the charge attack
+*/
+mframe_t fixbot_frames_attack2 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+
+	ai_charge,	-10, NULL,
+	ai_charge,  -10, NULL,
+	ai_charge,	-10, NULL,
+	ai_charge,  -10, NULL,
+	ai_charge,	-10, NULL,
+	ai_charge,  -10, NULL,
+	ai_charge,	-10, NULL,
+	ai_charge,  -10, NULL,
+	ai_charge,	-10, NULL,
+	ai_charge,  -10, NULL,
+
+	ai_charge,	0,	fixbot_fire_blaster,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,  0,  NULL,
+	ai_charge,  0,  NULL,
+
+	ai_charge,  0,  NULL
+};
+mmove_t fixbot_move_attack2 = {FRAME_charging_01, FRAME_charging_31, fixbot_frames_attack2, fixbot_run};
+
+void weldstate (edict_t *self)
+{
+	if (self->s.frame == FRAME_weldstart_10)
+		self->monsterinfo.currentmove = &fixbot_move_weld;
+	else if (self->s.frame == FRAME_weldmiddle_07)
+	{
+		if (self->goalentity->health < 0) 
+		{
+			self->enemy->owner = NULL;
+			self->monsterinfo.currentmove = &fixbot_move_weld_end;
+		}
+		else
+			self->goalentity->health-=10;
+	}
+	else
+	{
+		self->goalentity = self->enemy = NULL;
+		self->monsterinfo.currentmove = &fixbot_move_stand;
+	}
+}
+
+void ai_move2 (edict_t *self, float dist)
+{
+	vec3_t v;
+	
+	if (dist)
+		M_walkmove (self, self->s.angles[YAW], dist);
+
+	VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
+	self->ideal_yaw = vectoyaw(v);	
+	M_ChangeYaw (self);
+};
+
+mframe_t fixbot_frames_weld_start [] = 
+{
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, NULL,
+	ai_move2, 0, weldstate
+};
+mmove_t fixbot_move_weld_start = {FRAME_weldstart_01, FRAME_weldstart_10, fixbot_frames_weld_start, NULL};
+
+mframe_t fixbot_frames_weld [] =
+{
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, fixbot_fire_welder,
+	ai_move2, 0, weldstate
+};
+mmove_t fixbot_move_weld = {FRAME_weldmiddle_01, FRAME_weldmiddle_07, fixbot_frames_weld, NULL};
+
+mframe_t fixbot_frames_weld_end [] =
+{
+	ai_move2, -2, NULL,
+	ai_move2, -2, NULL,
+	ai_move2, -2, NULL,
+	ai_move2, -2, NULL,
+	ai_move2, -2, NULL,
+	ai_move2, -2, NULL,
+	ai_move2, -2, weldstate
+};
+mmove_t fixbot_move_weld_end = {FRAME_weldend_01, FRAME_weldend_07, fixbot_frames_weld_end, NULL};
+
+
+void fixbot_fire_welder (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	end;
+	vec3_t	dir;
+	vec3_t  vec;
+	float	r;
+	
+	if (!self->enemy)
+		return;
+
+
+	vec[0]=24.0;
+	vec[1]=-0.8;
+	vec[2]=-10.0;
+
+	AngleVectors (self->s.angles, forward, right, up);
+	G_ProjectSource (self->s.origin, vec, forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	
+	VectorSubtract (end, start, dir);
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_WELDING_SPARKS);
+	gi.WriteByte (10);
+	gi.WritePosition (start);
+	gi.WriteDir (vec3_origin);
+	gi.WriteByte (0xe0 + (rand()&7));
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+
+	if (qrandom() > 0.8)
+	{
+		r = qrandom();
+
+		if (r < 0.33)
+			gi.sound (self, CHAN_VOICE, sound_weld1, 1, ATTN_IDLE, 0);
+		else if (r < 0.66)
+			gi.sound (self, CHAN_VOICE, sound_weld2, 1, ATTN_IDLE, 0);
+		else
+			gi.sound (self, CHAN_VOICE, sound_weld3, 1, ATTN_IDLE, 0);
+	}
+}
+
+void fixbot_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	end;
+	vec3_t	dir;
+
+	if (!visible(self, self->enemy))
+	{
+		self->monsterinfo.currentmove = &fixbot_move_run;	
+	}
+	
+	AngleVectors (self->s.angles, forward, right, up);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_fixbot_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 15, 1000, MZ2_fixbot_BLASTER_1, EF_BLASTER);
+	
+}
+
+void fixbot_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &fixbot_move_stand;
+}
+
+void fixbot_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &fixbot_move_stand;
+	else
+		self->monsterinfo.currentmove = &fixbot_move_run;
+}
+
+void fixbot_walk (edict_t *self)
+{
+	vec3_t	vec;
+	int		len;
+
+	if (strcmp (self->goalentity->classname, "object_repair") == 0)
+	{
+		VectorSubtract (self->s.origin, self->goalentity->s.origin, vec);
+		len = VectorLength (vec);
+		if (len < 32)
+		{
+			self->monsterinfo.currentmove = &fixbot_move_weld_start;
+			return;
+		}
+	}
+	self->monsterinfo.currentmove = &fixbot_move_walk;
+}
+
+void fixbot_start_attack (edict_t *self)
+{
+	self->monsterinfo.currentmove = &fixbot_move_start_attack;
+}
+
+void fixbot_attack(edict_t *self)
+{
+	vec3_t	vec;
+	int		len;
+	
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		if (!visible (self, self->goalentity))
+			return;
+		VectorSubtract (self->s.origin, self->enemy->s.origin, vec);
+		len = VectorLength(vec);
+		if (len > 128)
+			return;
+		else
+			self->monsterinfo.currentmove = &fixbot_move_laserattack;
+	}
+	else
+		self->monsterinfo.currentmove = &fixbot_move_attack2;
+	
+}
+
+
+void fixbot_pain (edict_t *self, edict_t *, float, int damage)
+{
+//	if (self->health < (self->max_health / 2))
+//		self->s.skinnum = 1;
+	// gi.dprintf("pain\n");
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+
+	if (damage <= 10)
+		self->monsterinfo.currentmove = &fixbot_move_pain3;
+	else if (damage <= 25)
+		self->monsterinfo.currentmove = &fixbot_move_painb;
+	else
+		self->monsterinfo.currentmove = &fixbot_move_paina;
+}
+
+void fixbot_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void fixbot_die(edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	BecomeExplosion1(self);
+
+	// shards
+	
+}
+
+/*QUAKED monster_fixbot (1 .5 0) (-32 -32 -24) (32 32 24) Ambush Trigger_Spawn Fixit Takeoff Landing
+*/
+void SP_monster_fixbot (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("flyer/flypain1.wav");
+	sound_die = gi.soundindex ("flyer/flydeth1.wav");
+
+	sound_weld1 = gi.soundindex ("misc/welder1.wav");
+	sound_weld2 = gi.soundindex ("misc/welder2.wav");
+	sound_weld3 = gi.soundindex ("misc/welder3.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/fixbot/tris.md2");
+	
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 24);
+	
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 150;
+	self->mass = 150;
+
+	self->pain = fixbot_pain;
+	self->die = fixbot_die;
+
+	self->monsterinfo.stand = fixbot_stand;
+	self->monsterinfo.walk = fixbot_walk;
+	self->monsterinfo.run = fixbot_run;
+	self->monsterinfo.attack = fixbot_attack;
+	
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &fixbot_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+			
+	flymonster_start (self);
+				
+}
+
+
--- /dev/null
+++ b/xatrix/m_fixbot.h
@@ -1,0 +1,218 @@
+// ./fixbot
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_charging_01		0
+#define FRAME_charging_02		1
+#define FRAME_charging_03		2
+#define FRAME_charging_04		3
+#define FRAME_charging_05		4
+#define FRAME_charging_06		5
+#define FRAME_charging_07		6
+#define FRAME_charging_08		7
+#define FRAME_charging_09		8
+#define FRAME_charging_10		9
+#define FRAME_charging_11		10
+#define FRAME_charging_12		11
+#define FRAME_charging_13		12
+#define FRAME_charging_14		13
+#define FRAME_charging_15		14
+#define FRAME_charging_16		15
+#define FRAME_charging_17		16
+#define FRAME_charging_18		17
+#define FRAME_charging_19		18
+#define FRAME_charging_20		19
+#define FRAME_charging_21		20
+#define FRAME_charging_22		21
+#define FRAME_charging_23		22
+#define FRAME_charging_24		23
+#define FRAME_charging_25		24
+#define FRAME_charging_26		25
+#define FRAME_charging_27		26
+#define FRAME_charging_28		27
+#define FRAME_charging_29		28
+#define FRAME_charging_30		29
+#define FRAME_charging_31		30
+#define FRAME_landing_01		31
+#define FRAME_landing_02		32
+#define FRAME_landing_03		33
+#define FRAME_landing_04		34
+#define FRAME_landing_05		35
+#define FRAME_landing_06		36
+#define FRAME_landing_07		37
+#define FRAME_landing_08		38
+#define FRAME_landing_09		39
+#define FRAME_landing_10		40
+#define FRAME_landing_11		41
+#define FRAME_landing_12		42
+#define FRAME_landing_13		43
+#define FRAME_landing_14		44
+#define FRAME_landing_15		45
+#define FRAME_landing_16		46
+#define FRAME_landing_17		47
+#define FRAME_landing_18		48
+#define FRAME_landing_19		49
+#define FRAME_landing_20		50
+#define FRAME_landing_21		51
+#define FRAME_landing_22		52
+#define FRAME_landing_23		53
+#define FRAME_landing_24		54
+#define FRAME_landing_25		55
+#define FRAME_landing_26		56
+#define FRAME_landing_27		57
+#define FRAME_landing_28		58
+#define FRAME_landing_29		59
+#define FRAME_landing_30		60
+#define FRAME_landing_31		61
+#define FRAME_landing_32		62
+#define FRAME_landing_33		63
+#define FRAME_landing_34		64
+#define FRAME_landing_35		65
+#define FRAME_landing_36		66
+#define FRAME_landing_37		67
+#define FRAME_landing_38		68
+#define FRAME_landing_39		69
+#define FRAME_landing_40		70
+#define FRAME_landing_41		71
+#define FRAME_landing_42		72
+#define FRAME_landing_43		73
+#define FRAME_landing_44		74
+#define FRAME_landing_45		75
+#define FRAME_landing_46		76
+#define FRAME_landing_47		77
+#define FRAME_landing_48		78
+#define FRAME_landing_49		79
+#define FRAME_landing_50		80
+#define FRAME_landing_51		81
+#define FRAME_landing_52		82
+#define FRAME_landing_53		83
+#define FRAME_landing_54		84
+#define FRAME_landing_55		85
+#define FRAME_landing_56		86
+#define FRAME_landing_57		87
+#define FRAME_landing_58		88
+#define FRAME_pushback_01		89
+#define FRAME_pushback_02		90
+#define FRAME_pushback_03		91
+#define FRAME_pushback_04		92
+#define FRAME_pushback_05		93
+#define FRAME_pushback_06		94
+#define FRAME_pushback_07		95
+#define FRAME_pushback_08		96
+#define FRAME_pushback_09		97
+#define FRAME_pushback_10		98
+#define FRAME_pushback_11		99
+#define FRAME_pushback_12		100
+#define FRAME_pushback_13		101
+#define FRAME_pushback_14		102
+#define FRAME_pushback_15		103
+#define FRAME_pushback_16		104
+#define FRAME_takeoff_01		105
+#define FRAME_takeoff_02		106
+#define FRAME_takeoff_03		107
+#define FRAME_takeoff_04		108
+#define FRAME_takeoff_05		109
+#define FRAME_takeoff_06		110
+#define FRAME_takeoff_07		111
+#define FRAME_takeoff_08		112
+#define FRAME_takeoff_09		113
+#define FRAME_takeoff_10		114
+#define FRAME_takeoff_11		115
+#define FRAME_takeoff_12		116
+#define FRAME_takeoff_13		117
+#define FRAME_takeoff_14		118
+#define FRAME_takeoff_15		119
+#define FRAME_takeoff_16		120
+#define FRAME_ambient_01		121
+#define FRAME_ambient_02		122
+#define FRAME_ambient_03		123
+#define FRAME_ambient_04		124
+#define FRAME_ambient_05		125
+#define FRAME_ambient_06		126
+#define FRAME_ambient_07		127
+#define FRAME_ambient_08		128
+#define FRAME_ambient_09		129
+#define FRAME_ambient_10		130
+#define FRAME_ambient_11		131
+#define FRAME_ambient_12		132
+#define FRAME_ambient_13		133
+#define FRAME_ambient_14		134
+#define FRAME_ambient_15		135
+#define FRAME_ambient_16		136
+#define FRAME_ambient_17		137
+#define FRAME_ambient_18		138
+#define FRAME_ambient_19		139
+#define FRAME_paina_01		140
+#define FRAME_paina_02		141
+#define FRAME_paina_03		142
+#define FRAME_paina_04		143
+#define FRAME_paina_05		144
+#define FRAME_paina_06		145
+#define FRAME_painb_01		146
+#define FRAME_painb_02		147
+#define FRAME_painb_03		148
+#define FRAME_painb_04		149
+#define FRAME_painb_05		150
+#define FRAME_painb_06		151
+#define FRAME_painb_07		152
+#define FRAME_painb_08		153
+#define FRAME_pickup_01		154
+#define FRAME_pickup_02		155
+#define FRAME_pickup_03		156
+#define FRAME_pickup_04		157
+#define FRAME_pickup_05		158
+#define FRAME_pickup_06		159
+#define FRAME_pickup_07		160
+#define FRAME_pickup_08		161
+#define FRAME_pickup_09		162
+#define FRAME_pickup_10		163
+#define FRAME_pickup_11		164
+#define FRAME_pickup_12		165
+#define FRAME_pickup_13		166
+#define FRAME_pickup_14		167
+#define FRAME_pickup_15		168
+#define FRAME_pickup_16		169
+#define FRAME_pickup_17		170
+#define FRAME_pickup_18		171
+#define FRAME_pickup_19		172
+#define FRAME_pickup_20		173
+#define FRAME_pickup_21		174
+#define FRAME_pickup_22		175
+#define FRAME_pickup_23		176
+#define FRAME_pickup_24		177
+#define FRAME_pickup_25		178
+#define FRAME_pickup_26		179
+#define FRAME_pickup_27		180
+#define FRAME_freeze_01		181
+#define FRAME_shoot_01		182
+#define FRAME_shoot_02		183
+#define FRAME_shoot_03		184
+#define FRAME_shoot_04		185
+#define FRAME_shoot_05		186
+#define FRAME_shoot_06		187
+#define FRAME_weldstart_01		188
+#define FRAME_weldstart_02		189
+#define FRAME_weldstart_03		190
+#define FRAME_weldstart_04		191
+#define FRAME_weldstart_05		192
+#define FRAME_weldstart_06		193
+#define FRAME_weldstart_07		194
+#define FRAME_weldstart_08		195
+#define FRAME_weldstart_09		196
+#define FRAME_weldstart_10		197
+#define FRAME_weldmiddle_01		198
+#define FRAME_weldmiddle_02		199
+#define FRAME_weldmiddle_03		200
+#define FRAME_weldmiddle_04		201
+#define FRAME_weldmiddle_05		202
+#define FRAME_weldmiddle_06		203
+#define FRAME_weldmiddle_07		204
+#define FRAME_weldend_01		205
+#define FRAME_weldend_02		206
+#define FRAME_weldend_03		207
+#define FRAME_weldend_04		208
+#define FRAME_weldend_05		209
+#define FRAME_weldend_06		210
+#define FRAME_weldend_07		211
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_flash.c
@@ -1,0 +1,471 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+// this file is included in both the game dll and quake2,
+// the game needs it to source shot locations, the client
+// needs it to position muzzle flashes
+vec3_t monster_flash_offset [] =
+{
+// flash 0 is not used
+	0.0, 0.0, 0.0,
+
+// MZ2_TANK_BLASTER_1				1
+	20.7, -18.5, 28.7,
+// MZ2_TANK_BLASTER_2				2
+	16.6, -21.5, 30.1,
+// MZ2_TANK_BLASTER_3				3
+	11.8, -23.9, 32.1,
+// MZ2_TANK_MACHINEGUN_1			4
+	22.9, -0.7, 25.3,
+// MZ2_TANK_MACHINEGUN_2			5
+	22.2, 6.2, 22.3,
+// MZ2_TANK_MACHINEGUN_3			6
+	19.4, 13.1, 18.6,
+// MZ2_TANK_MACHINEGUN_4			7
+	19.4, 18.8, 18.6,
+// MZ2_TANK_MACHINEGUN_5			8
+	17.9, 25.0, 18.6,
+// MZ2_TANK_MACHINEGUN_6			9
+	14.1, 30.5, 20.6,
+// MZ2_TANK_MACHINEGUN_7			10
+	9.3, 35.3, 22.1,
+// MZ2_TANK_MACHINEGUN_8			11
+	4.7, 38.4, 22.1,
+// MZ2_TANK_MACHINEGUN_9			12
+	-1.1, 40.4, 24.1,
+// MZ2_TANK_MACHINEGUN_10			13
+	-6.5, 41.2, 24.1,
+// MZ2_TANK_MACHINEGUN_11			14
+	3.2, 40.1, 24.7,
+// MZ2_TANK_MACHINEGUN_12			15
+	11.7, 36.7, 26.0,
+// MZ2_TANK_MACHINEGUN_13			16
+	18.9, 31.3, 26.0,
+// MZ2_TANK_MACHINEGUN_14			17
+	24.4, 24.4, 26.4,
+// MZ2_TANK_MACHINEGUN_15			18
+	27.1, 17.1, 27.2,
+// MZ2_TANK_MACHINEGUN_16			19
+	28.5, 9.1, 28.0,
+// MZ2_TANK_MACHINEGUN_17			20
+	27.1, 2.2, 28.0,
+// MZ2_TANK_MACHINEGUN_18			21
+	24.9, -2.8, 28.0,
+// MZ2_TANK_MACHINEGUN_19			22
+	21.6, -7.0, 26.4,
+// MZ2_TANK_ROCKET_1				23
+	6.2, 29.1, 49.1,
+// MZ2_TANK_ROCKET_2				24
+	6.9, 23.8, 49.1,
+// MZ2_TANK_ROCKET_3				25
+	8.3, 17.8, 49.5,
+
+// MZ2_INFANTRY_MACHINEGUN_1		26
+	26.6, 7.1, 13.1,
+// MZ2_INFANTRY_MACHINEGUN_2		27
+	18.2, 7.5, 15.4,
+// MZ2_INFANTRY_MACHINEGUN_3		28
+	17.2, 10.3, 17.9,
+// MZ2_INFANTRY_MACHINEGUN_4		29
+	17.0, 12.8, 20.1,
+// MZ2_INFANTRY_MACHINEGUN_5		30
+	15.1, 14.1, 21.8,
+// MZ2_INFANTRY_MACHINEGUN_6		31
+	11.8, 17.2, 23.1,
+// MZ2_INFANTRY_MACHINEGUN_7		32
+	11.4, 20.2, 21.0,
+// MZ2_INFANTRY_MACHINEGUN_8		33
+	9.0, 23.0, 18.9,
+// MZ2_INFANTRY_MACHINEGUN_9		34
+	13.9, 18.6, 17.7,
+// MZ2_INFANTRY_MACHINEGUN_10		35
+	15.4, 15.6, 15.8,
+// MZ2_INFANTRY_MACHINEGUN_11		36
+	10.2, 15.2, 25.1,
+// MZ2_INFANTRY_MACHINEGUN_12		37
+	-1.9, 15.1, 28.2,
+// MZ2_INFANTRY_MACHINEGUN_13		38
+	-12.4, 13.0, 20.2,
+
+// MZ2_SOLDIER_BLASTER_1			39
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_2			40
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_1			41
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_2			42
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_1			43
+	10.6 * 1.2, 7.7 * 1.2, 7.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_2			44
+	21.1 * 1.2, 3.6 * 1.2, 19.0 * 1.2,
+
+// MZ2_GUNNER_MACHINEGUN_1			45
+	30.1 * 1.15, 3.9 * 1.15, 19.6 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_2			46
+	29.1 * 1.15, 2.5 * 1.15, 20.7 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_3			47
+	28.2 * 1.15, 2.5 * 1.15, 22.2 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_4			48
+	28.2 * 1.15, 3.6 * 1.15, 22.0 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_5			49
+	26.9 * 1.15, 2.0 * 1.15, 23.4 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_6			50
+	26.5 * 1.15, 0.6 * 1.15, 20.8 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_7			51
+	26.9 * 1.15, 0.5 * 1.15, 21.5 * 1.15,
+// MZ2_GUNNER_MACHINEGUN_8			52
+	29.0 * 1.15, 2.4 * 1.15, 19.5 * 1.15,
+// MZ2_GUNNER_GRENADE_1				53
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_2				54
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_3				55
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+// MZ2_GUNNER_GRENADE_4				56
+	4.6 * 1.15, -16.8 * 1.15, 7.3 * 1.15,
+
+// MZ2_CHICK_ROCKET_1				57
+//	-24.8, -9.0, 39.0,
+	24.8, -9.0, 39.0,			// PGM - this was incorrect in Q2
+
+// MZ2_FLYER_BLASTER_1				58
+	12.1, 13.4, -14.5,
+// MZ2_FLYER_BLASTER_2				59
+	12.1, -7.4, -14.5,
+
+// MZ2_MEDIC_BLASTER_1				60
+	12.1, 5.4, 16.5,
+
+// MZ2_GLADIATOR_RAILGUN_1			61
+	30.0, 18.0, 28.0,
+
+// MZ2_HOVER_BLASTER_1				62
+	32.5, -0.8, 10.0,
+
+// MZ2_ACTOR_MACHINEGUN_1			63
+	18.4, 7.4, 9.6,
+
+// MZ2_SUPERTANK_MACHINEGUN_1		64
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_2		65
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_3		66
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_4		67
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_5		68
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_MACHINEGUN_6		69
+	30.0, 30.0, 88.5,
+// MZ2_SUPERTANK_ROCKET_1			70
+	16.0, -22.5, 91.2,
+// MZ2_SUPERTANK_ROCKET_2			71
+	16.0, -33.4, 86.7,
+// MZ2_SUPERTANK_ROCKET_3			72
+	16.0, -42.8, 83.3,
+
+// --- Start Xian Stuff ---
+// MZ2_BOSS2_MACHINEGUN_L1			73
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L2			74
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L3			75
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L4			76
+	32,	-40,	70,
+// MZ2_BOSS2_MACHINEGUN_L5			77
+	32,	-40,	70,
+// --- End Xian Stuff
+
+// MZ2_BOSS2_ROCKET_1				78
+	22.0, 16.0, 10.0,
+// MZ2_BOSS2_ROCKET_2				79
+	22.0, 8.0, 10.0,
+// MZ2_BOSS2_ROCKET_3				80
+	22.0, -8.0, 10.0,
+// MZ2_BOSS2_ROCKET_4				81
+	22.0, -16.0, 10.0,
+
+// MZ2_FLOAT_BLASTER_1				82
+	32.5, -0.8, 10,
+
+// MZ2_SOLDIER_BLASTER_3			83
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_3			84
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_3			85
+	20.8 * 1.2, 10.1 * 1.2, -2.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_4			86
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_4			87
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_4			88
+	7.6 * 1.2, 9.3 * 1.2, 0.8 * 1.2,
+// MZ2_SOLDIER_BLASTER_5			89
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_5			90
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_5			91
+	30.5 * 1.2, 9.9 * 1.2, -18.7 * 1.2,
+// MZ2_SOLDIER_BLASTER_6			92
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_6			93
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_6			94
+	27.6 * 1.2, 3.4 * 1.2, -10.4 * 1.2,
+// MZ2_SOLDIER_BLASTER_7			95
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_7			96
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_7			97
+	28.9 * 1.2, 4.6 * 1.2, -8.1 * 1.2,
+// MZ2_SOLDIER_BLASTER_8			98
+//	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+	31.5 * 1.2, 9.6 * 1.2, 10.1 * 1.2,
+// MZ2_SOLDIER_SHOTGUN_8			99
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+// MZ2_SOLDIER_MACHINEGUN_8			100
+	34.5 * 1.2, 9.6 * 1.2, 6.1 * 1.2,
+
+// --- Xian shit below ---
+// MZ2_MAKRON_BFG					101
+	17,		-19.5,	62.9,
+// MZ2_MAKRON_BLASTER_1				102
+	-3.6,	-24.1,	59.5,
+// MZ2_MAKRON_BLASTER_2				103
+	-1.6,	-19.3,	59.5,
+// MZ2_MAKRON_BLASTER_3				104
+	-0.1,	-14.4,	59.5,		
+// MZ2_MAKRON_BLASTER_4				105
+	2.0,	-7.6,	59.5,	
+// MZ2_MAKRON_BLASTER_5				106
+	3.4,	1.3,	59.5,
+// MZ2_MAKRON_BLASTER_6				107
+	3.7,	11.1,	59.5,	
+// MZ2_MAKRON_BLASTER_7				108
+	-0.3,	22.3,	59.5,
+// MZ2_MAKRON_BLASTER_8				109
+	-6,		33,		59.5,
+// MZ2_MAKRON_BLASTER_9				110
+	-9.3,	36.4,	59.5,
+// MZ2_MAKRON_BLASTER_10			111
+	-7,		35,		59.5,
+// MZ2_MAKRON_BLASTER_11			112
+	-2.1,	29,		59.5,
+// MZ2_MAKRON_BLASTER_12			113
+	3.9,	17.3,	59.5,
+// MZ2_MAKRON_BLASTER_13			114
+	6.1,	5.8,	59.5,
+// MZ2_MAKRON_BLASTER_14			115
+	5.9,	-4.4,	59.5,
+// MZ2_MAKRON_BLASTER_15			116
+	4.2,	-14.1,	59.5,		
+// MZ2_MAKRON_BLASTER_16			117
+	2.4,	-18.8,	59.5,
+// MZ2_MAKRON_BLASTER_17			118
+	-1.8,	-25.5,	59.5,
+// MZ2_MAKRON_RAILGUN_1				119
+	-17.3,	7.8,	72.4,
+
+// MZ2_JORG_MACHINEGUN_L1			120
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L2			121
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L3			122
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L4			123
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L5			124
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_L6			125
+	78.5,	-47.1,	96,			
+// MZ2_JORG_MACHINEGUN_R1			126
+	78.5,	46.7,  96,			
+// MZ2_JORG_MACHINEGUN_R2			127
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R3			128
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R4			129
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R5			130
+	78.5,	46.7,	96,			
+// MZ2_JORG_MACHINEGUN_R6			131
+	78.5,	46.7,	96,			
+// MZ2_JORG_BFG_1					132
+	6.3,	-9,		111.2,
+
+// MZ2_BOSS2_MACHINEGUN_R1			73
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R2			74
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R3			75
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R4			76
+	32,	40,	70,
+// MZ2_BOSS2_MACHINEGUN_R5			77
+	32,	40,	70,
+
+// --- End Xian Shit ---
+
+// ROGUE
+// note that the above really ends at 137
+// carrier machineguns
+// MZ2_CARRIER_MACHINEGUN_L1
+	56,	-32, 32,
+// MZ2_CARRIER_MACHINEGUN_R1
+	56,	32, 32,
+// MZ2_CARRIER_GRENADE
+	42,	24, 50,
+// MZ2_TURRET_MACHINEGUN			141
+	16, 0, 0,
+// MZ2_TURRET_ROCKET				142
+	16, 0, 0,
+// MZ2_TURRET_BLASTER				143
+	16, 0, 0,
+// MZ2_STALKER_BLASTER				144
+	24, 0, 6,
+// MZ2_DAEDALUS_BLASTER				145
+	32.5, -0.8, 10.0,
+// MZ2_MEDIC_BLASTER_2				146
+	12.1, 5.4, 16.5,
+// MZ2_CARRIER_RAILGUN				147
+	32, 0, 6, 
+// MZ2_WIDOW_DISRUPTOR				148
+	57.72, 14.50, 88.81,
+// MZ2_WIDOW_BLASTER				149
+	56,	32, 32,
+// MZ2_WIDOW_RAIL					150
+	62, -20, 84, 
+// MZ2_WIDOW_PLASMABEAM				151		// PMM - not used!
+	32, 0, 6, 
+// MZ2_CARRIER_MACHINEGUN_L2		152
+	61,	-32, 12,
+// MZ2_CARRIER_MACHINEGUN_R2		153
+	61,	32, 12,
+// MZ2_WIDOW_RAIL_LEFT				154
+	17, -62, 91, 
+// MZ2_WIDOW_RAIL_RIGHT				155
+	68, 12, 86, 
+// MZ2_WIDOW_BLASTER_SWEEP1			156			pmm - the sweeps need to be in sequential order
+	47.5, 56, 89,
+// MZ2_WIDOW_BLASTER_SWEEP2			157
+	54, 52, 91,
+// MZ2_WIDOW_BLASTER_SWEEP3			158
+	58, 40, 91,
+// MZ2_WIDOW_BLASTER_SWEEP4			159
+	68, 30, 88,
+// MZ2_WIDOW_BLASTER_SWEEP5			160
+	74, 20, 88,
+// MZ2_WIDOW_BLASTER_SWEEP6			161
+	73, 11, 87,
+// MZ2_WIDOW_BLASTER_SWEEP7			162
+	73, 3, 87,
+// MZ2_WIDOW_BLASTER_SWEEP8			163
+	70, -12, 87,
+// MZ2_WIDOW_BLASTER_SWEEP9			164
+	67, -20, 90,
+// MZ2_WIDOW_BLASTER_100			165
+	-20, 76, 90,
+// MZ2_WIDOW_BLASTER_90				166
+	-8, 74, 90,
+// MZ2_WIDOW_BLASTER_80				167
+	0, 72, 90,
+// MZ2_WIDOW_BLASTER_70				168		d06
+	10, 71, 89,
+// MZ2_WIDOW_BLASTER_60				169		d07
+	23, 70, 87,
+// MZ2_WIDOW_BLASTER_50				170		d08
+	32, 64, 85,
+// MZ2_WIDOW_BLASTER_40				171
+	40, 58, 84,
+// MZ2_WIDOW_BLASTER_30				172		d10
+	48, 50, 83,
+// MZ2_WIDOW_BLASTER_20				173
+	54, 42, 82,
+// MZ2_WIDOW_BLASTER_10				174		d12
+	56, 34, 82,
+// MZ2_WIDOW_BLASTER_0				175
+	58, 26, 82,
+// MZ2_WIDOW_BLASTER_10L			176		d14
+	60, 16, 82,
+// MZ2_WIDOW_BLASTER_20L			177
+	59, 6, 81,
+// MZ2_WIDOW_BLASTER_30L			178		d16
+	58, -2, 80,
+// MZ2_WIDOW_BLASTER_40L			179
+	57, -10, 79,
+// MZ2_WIDOW_BLASTER_50L			180		d18
+	54, -18, 78,
+// MZ2_WIDOW_BLASTER_60L			181
+	42, -32, 80,
+// MZ2_WIDOW_BLASTER_70L			182		d20
+	36, -40, 78,
+// MZ2_WIDOW_RUN_1					183
+	68.4, 10.88, 82.08,
+// MZ2_WIDOW_RUN_2					184
+	68.51, 8.64, 85.14,
+// MZ2_WIDOW_RUN_3					185
+	68.66, 6.38, 88.78,
+// MZ2_WIDOW_RUN_4					186
+	68.73, 5.1, 84.47,
+// MZ2_WIDOW_RUN_5					187
+	68.82, 4.79, 80.52,
+// MZ2_WIDOW_RUN_6					188
+	68.77, 6.11, 85.37,
+// MZ2_WIDOW_RUN_7					189
+	68.67, 7.99, 90.24,
+// MZ2_WIDOW_RUN_8					190
+	68.55, 9.54, 87.36,
+// MZ2_CARRIER_ROCKET_1				191
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_2				192
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_3				193
+	0, 0, -5,
+// MZ2_CARRIER_ROCKET_4				194
+	0, 0, -5,
+// MZ2_WIDOW2_BEAMER_1				195
+//	72.13, -17.63, 93.77,
+	69.00, -17.63, 93.77,
+// MZ2_WIDOW2_BEAMER_2				196
+//	71.46, -17.08, 89.82,
+	69.00, -17.08, 89.82,
+// MZ2_WIDOW2_BEAMER_3				197
+//	71.47, -18.40, 90.70,
+	69.00, -18.40, 90.70,
+// MZ2_WIDOW2_BEAMER_4				198
+//	71.96, -18.34, 94.32,
+	69.00, -18.34, 94.32,
+// MZ2_WIDOW2_BEAMER_5				199
+//	72.25, -18.30, 97.98,
+	69.00, -18.30, 97.98,
+// MZ2_WIDOW2_BEAM_SWEEP_1			200
+	45.04, -59.02, 92.24,
+// MZ2_WIDOW2_BEAM_SWEEP_2			201
+	50.68, -54.70, 91.96,
+// MZ2_WIDOW2_BEAM_SWEEP_3			202
+	56.57, -47.72, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_4			203
+	61.75, -38.75, 91.38,
+// MZ2_WIDOW2_BEAM_SWEEP_5			204
+	65.55, -28.76, 91.24,
+// MZ2_WIDOW2_BEAM_SWEEP_6			205
+	67.79, -18.90, 91.22,
+// MZ2_WIDOW2_BEAM_SWEEP_7			206
+	68.60, -9.52, 91.23,
+// MZ2_WIDOW2_BEAM_SWEEP_8			207
+	68.08, 0.18, 91.32,
+// MZ2_WIDOW2_BEAM_SWEEP_9			208
+	66.14, 9.79, 91.44,
+// MZ2_WIDOW2_BEAM_SWEEP_10			209
+	62.77, 18.91, 91.65,
+// MZ2_WIDOW2_BEAM_SWEEP_11			210
+	58.29, 27.11, 92.00,
+
+// end of table
+	0.0, 0.0, 0.0
+};
--- /dev/null
+++ b/xatrix/m_flipper.c
@@ -1,0 +1,380 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_flipper.h"
+
+
+static int	sound_chomp;
+static int	sound_attack;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_search;
+static int	sound_sight;
+
+
+void flipper_stand (edict_t *self);
+
+mframe_t flipper_frames_stand [] =
+{
+	ai_stand, 0, NULL
+};
+	
+mmove_t	flipper_move_stand = {FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, NULL};
+
+void flipper_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flipper_move_stand;
+}
+
+#define FLIPPER_RUN_SPEED	24
+
+mframe_t flipper_frames_run [] =
+{
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 6
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 10
+
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,	// 20
+
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL,
+	ai_run, FLIPPER_RUN_SPEED, NULL		// 29
+};
+mmove_t flipper_move_run_loop = {FRAME_flpver06, FRAME_flpver29, flipper_frames_run, NULL};
+
+void flipper_run_loop (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_run_loop;
+}
+
+mframe_t flipper_frames_run_start [] =
+{
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL
+};
+mmove_t flipper_move_run_start = {FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop};
+
+void flipper_run (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_run_start;
+}
+
+/* Standard Swimming */ 
+mframe_t flipper_frames_walk [] =
+{
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 4, NULL
+};
+mmove_t flipper_move_walk = {FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, NULL};
+
+void flipper_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_walk;
+}
+
+mframe_t flipper_frames_start_run [] =
+{
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, NULL,
+	ai_run, 8, flipper_run
+};
+mmove_t flipper_move_start_run = {FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, NULL};
+
+void flipper_start_run (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_start_run;
+}
+
+mframe_t flipper_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0, NULL
+};
+mmove_t flipper_move_pain2 = {FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run};
+
+mframe_t flipper_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0, NULL
+};
+mmove_t flipper_move_pain1 = {FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run};
+
+void flipper_bite (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	fire_hit (self, aim, 5, 0);
+}
+
+void flipper_preattack (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0);
+}
+
+mframe_t flipper_frames_attack [] =
+{
+	ai_charge, 0,	flipper_preattack,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	flipper_bite,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	flipper_bite,
+	ai_charge, 0,	NULL
+};
+mmove_t flipper_move_attack = {FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run};
+
+void flipper_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &flipper_move_attack;
+}
+
+void flipper_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = (rand() + 1) % 2;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flipper_move_pain1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flipper_move_pain2;
+	}
+}
+
+void flipper_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t flipper_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t flipper_move_death = {FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead};
+
+void flipper_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void flipper_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &flipper_move_death;
+}
+
+/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_flipper (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1		= gi.soundindex ("flipper/flppain1.wav");	
+	sound_pain2		= gi.soundindex ("flipper/flppain2.wav");	
+	sound_death		= gi.soundindex ("flipper/flpdeth1.wav");	
+	sound_chomp		= gi.soundindex ("flipper/flpatck1.wav");
+	sound_attack	= gi.soundindex ("flipper/flpatck2.wav");
+	sound_idle		= gi.soundindex ("flipper/flpidle1.wav");
+	sound_search	= gi.soundindex ("flipper/flpsrch1.wav");
+	sound_sight		= gi.soundindex ("flipper/flpsght1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/flipper/tris.md2");
+	VectorSet (self->mins, -16, -16, 0);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 50;
+	self->gib_health = -30;
+	self->mass = 100;
+
+	self->pain = flipper_pain;
+	self->die = flipper_die;
+
+	self->monsterinfo.stand = flipper_stand;
+	self->monsterinfo.walk = flipper_walk;
+	self->monsterinfo.run = flipper_start_run;
+	self->monsterinfo.melee = flipper_melee;
+	self->monsterinfo.sight = flipper_sight;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &flipper_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	swimmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_flipper.h
@@ -1,0 +1,166 @@
+// G:\quake2\baseq2\models/monsters/flipper
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_flpbit01        	0
+#define FRAME_flpbit02        	1
+#define FRAME_flpbit03        	2
+#define FRAME_flpbit04        	3
+#define FRAME_flpbit05        	4
+#define FRAME_flpbit06        	5
+#define FRAME_flpbit07        	6
+#define FRAME_flpbit08        	7
+#define FRAME_flpbit09        	8
+#define FRAME_flpbit10        	9
+#define FRAME_flpbit11        	10
+#define FRAME_flpbit12        	11
+#define FRAME_flpbit13        	12
+#define FRAME_flpbit14        	13
+#define FRAME_flpbit15        	14
+#define FRAME_flpbit16        	15
+#define FRAME_flpbit17        	16
+#define FRAME_flpbit18        	17
+#define FRAME_flpbit19        	18
+#define FRAME_flpbit20        	19
+#define FRAME_flptal01        	20
+#define FRAME_flptal02        	21
+#define FRAME_flptal03        	22
+#define FRAME_flptal04        	23
+#define FRAME_flptal05        	24
+#define FRAME_flptal06        	25
+#define FRAME_flptal07        	26
+#define FRAME_flptal08        	27
+#define FRAME_flptal09        	28
+#define FRAME_flptal10        	29
+#define FRAME_flptal11        	30
+#define FRAME_flptal12        	31
+#define FRAME_flptal13        	32
+#define FRAME_flptal14        	33
+#define FRAME_flptal15        	34
+#define FRAME_flptal16        	35
+#define FRAME_flptal17        	36
+#define FRAME_flptal18        	37
+#define FRAME_flptal19        	38
+#define FRAME_flptal20        	39
+#define FRAME_flptal21        	40
+#define FRAME_flphor01        	41
+#define FRAME_flphor02        	42
+#define FRAME_flphor03        	43
+#define FRAME_flphor04        	44
+#define FRAME_flphor05        	45
+#define FRAME_flphor06        	46
+#define FRAME_flphor07        	47
+#define FRAME_flphor08        	48
+#define FRAME_flphor09        	49
+#define FRAME_flphor10        	50
+#define FRAME_flphor11        	51
+#define FRAME_flphor12        	52
+#define FRAME_flphor13        	53
+#define FRAME_flphor14        	54
+#define FRAME_flphor15        	55
+#define FRAME_flphor16        	56
+#define FRAME_flphor17        	57
+#define FRAME_flphor18        	58
+#define FRAME_flphor19        	59
+#define FRAME_flphor20        	60
+#define FRAME_flphor21        	61
+#define FRAME_flphor22        	62
+#define FRAME_flphor23        	63
+#define FRAME_flphor24        	64
+#define FRAME_flpver01        	65
+#define FRAME_flpver02        	66
+#define FRAME_flpver03        	67
+#define FRAME_flpver04        	68
+#define FRAME_flpver05        	69
+#define FRAME_flpver06        	70
+#define FRAME_flpver07        	71
+#define FRAME_flpver08        	72
+#define FRAME_flpver09        	73
+#define FRAME_flpver10        	74
+#define FRAME_flpver11        	75
+#define FRAME_flpver12        	76
+#define FRAME_flpver13        	77
+#define FRAME_flpver14        	78
+#define FRAME_flpver15        	79
+#define FRAME_flpver16        	80
+#define FRAME_flpver17        	81
+#define FRAME_flpver18        	82
+#define FRAME_flpver19        	83
+#define FRAME_flpver20        	84
+#define FRAME_flpver21        	85
+#define FRAME_flpver22        	86
+#define FRAME_flpver23        	87
+#define FRAME_flpver24        	88
+#define FRAME_flpver25        	89
+#define FRAME_flpver26        	90
+#define FRAME_flpver27        	91
+#define FRAME_flpver28        	92
+#define FRAME_flpver29        	93
+#define FRAME_flppn101        	94
+#define FRAME_flppn102        	95
+#define FRAME_flppn103        	96
+#define FRAME_flppn104        	97
+#define FRAME_flppn105        	98
+#define FRAME_flppn201        	99
+#define FRAME_flppn202        	100
+#define FRAME_flppn203        	101
+#define FRAME_flppn204        	102
+#define FRAME_flppn205        	103
+#define FRAME_flpdth01        	104
+#define FRAME_flpdth02        	105
+#define FRAME_flpdth03        	106
+#define FRAME_flpdth04        	107
+#define FRAME_flpdth05        	108
+#define FRAME_flpdth06        	109
+#define FRAME_flpdth07        	110
+#define FRAME_flpdth08        	111
+#define FRAME_flpdth09        	112
+#define FRAME_flpdth10        	113
+#define FRAME_flpdth11        	114
+#define FRAME_flpdth12        	115
+#define FRAME_flpdth13        	116
+#define FRAME_flpdth14        	117
+#define FRAME_flpdth15        	118
+#define FRAME_flpdth16        	119
+#define FRAME_flpdth17        	120
+#define FRAME_flpdth18        	121
+#define FRAME_flpdth19        	122
+#define FRAME_flpdth20        	123
+#define FRAME_flpdth21        	124
+#define FRAME_flpdth22        	125
+#define FRAME_flpdth23        	126
+#define FRAME_flpdth24        	127
+#define FRAME_flpdth25        	128
+#define FRAME_flpdth26        	129
+#define FRAME_flpdth27        	130
+#define FRAME_flpdth28        	131
+#define FRAME_flpdth29        	132
+#define FRAME_flpdth30        	133
+#define FRAME_flpdth31        	134
+#define FRAME_flpdth32        	135
+#define FRAME_flpdth33        	136
+#define FRAME_flpdth34        	137
+#define FRAME_flpdth35        	138
+#define FRAME_flpdth36        	139
+#define FRAME_flpdth37        	140
+#define FRAME_flpdth38        	141
+#define FRAME_flpdth39        	142
+#define FRAME_flpdth40        	143
+#define FRAME_flpdth41        	144
+#define FRAME_flpdth42        	145
+#define FRAME_flpdth43        	146
+#define FRAME_flpdth44        	147
+#define FRAME_flpdth45        	148
+#define FRAME_flpdth46        	149
+#define FRAME_flpdth47        	150
+#define FRAME_flpdth48        	151
+#define FRAME_flpdth49        	152
+#define FRAME_flpdth50        	153
+#define FRAME_flpdth51        	154
+#define FRAME_flpdth52        	155
+#define FRAME_flpdth53        	156
+#define FRAME_flpdth54        	157
+#define FRAME_flpdth55        	158
+#define FRAME_flpdth56        	159
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_float.c
@@ -1,0 +1,640 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_float.h"
+
+
+static int	sound_attack2;
+static int	sound_attack3;
+static int	sound_death1;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+
+
+void floater_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void floater_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//void floater_stand1 (edict_t *self);
+void floater_dead (edict_t *self);
+void floater_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+void floater_run (edict_t *self);
+void floater_wham (edict_t *self);
+void floater_zap (edict_t *self);
+
+
+void floater_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if ((self->s.frame == FRAME_attak104) || (self->s.frame == FRAME_attak107))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, effect);
+}
+
+
+mframe_t floater_frames_stand1 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	floater_move_stand1 = {FRAME_stand101, FRAME_stand152, floater_frames_stand1, NULL};
+
+mframe_t floater_frames_stand2 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	floater_move_stand2 = {FRAME_stand201, FRAME_stand252, floater_frames_stand2, NULL};
+
+void floater_stand (edict_t *self)
+{
+	if (qrandom() <= 0.5)		
+		self->monsterinfo.currentmove = &floater_move_stand1;
+	else
+		self->monsterinfo.currentmove = &floater_move_stand2;
+}
+
+mframe_t floater_frames_activate [] =
+{
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL,	
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_activate = {FRAME_actvat01, FRAME_actvat31, floater_frames_activate, NULL};
+
+mframe_t floater_frames_attack1 [] =
+{
+	ai_charge,	0,	NULL,			// Blaster attack
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_fire_blaster,			// BOOM (0, -25.8, 32.5)	-- LOOP Starts
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	floater_fire_blaster,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL			//							-- LOOP Ends
+};
+mmove_t floater_move_attack1 = {FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run};
+
+mframe_t floater_frames_attack2 [] =
+{
+	ai_charge,	0,	NULL,			// Claws
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_wham,			// WHAM (0, -45, 29.6)		-- LOOP Starts
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,			//							-- LOOP Ends
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t floater_move_attack2 = {FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run};
+
+mframe_t floater_frames_attack3 [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	floater_zap,		//								-- LOOP Starts
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,		//								-- LOOP Ends
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL
+};
+mmove_t floater_move_attack3 = {FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run};
+
+mframe_t floater_frames_death [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_death = {FRAME_death01, FRAME_death13, floater_frames_death, floater_dead};
+
+mframe_t floater_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain1 = {FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run};
+
+mframe_t floater_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain2 = {FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run};
+
+mframe_t floater_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t floater_move_pain3 = {FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run};
+
+mframe_t floater_frames_walk [] =
+{
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL
+};
+mmove_t	floater_move_walk = {FRAME_stand101, FRAME_stand152, floater_frames_walk, NULL};
+
+mframe_t floater_frames_run [] =
+{
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL,
+	ai_run, 13, NULL
+};
+mmove_t	floater_move_run = {FRAME_stand101, FRAME_stand152, floater_frames_run, NULL};
+
+void floater_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &floater_move_stand1;
+	else
+		self->monsterinfo.currentmove = &floater_move_run;
+}
+
+void floater_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &floater_move_walk;
+}
+
+void floater_wham (edict_t *self)
+{
+	static	vec3_t	aim = {MELEE_DISTANCE, 0, 0};
+	gi.sound (self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0);
+	fire_hit (self, aim, 5 + rand() % 6, -50);
+}
+
+void floater_zap (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	origin;
+	vec3_t	dir;
+	vec3_t	offset;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, dir);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	//FIXME use a flash and replace these two lines with the commented one
+	VectorSet (offset, 18.5, -0.9, 10);
+	G_ProjectSource (self->s.origin, offset, forward, right, origin);
+//	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, origin);
+
+	gi.sound (self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0);
+
+	//FIXME use the flash, Luke
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_SPLASH);
+	gi.WriteByte (32);
+	gi.WritePosition (origin);
+	gi.WriteDir (dir);
+	gi.WriteByte (1);	//sparks
+	gi.multicast (origin, MULTICAST_PVS);
+
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, 5 + rand() % 6, -10, DAMAGE_ENERGY, MOD_UNKNOWN);
+}
+
+void floater_attack(edict_t *self)
+{
+	self->monsterinfo.currentmove = &floater_move_attack1;
+}
+
+
+void floater_melee(edict_t *self)
+{
+	if (qrandom() < 0.5)		
+		self->monsterinfo.currentmove = &floater_move_attack3;
+	else
+		self->monsterinfo.currentmove = &floater_move_attack2;
+}
+
+
+void floater_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = (rand() + 1) % 3;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &floater_move_pain1;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &floater_move_pain2;
+	}
+}
+
+void floater_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+void floater_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+	BecomeExplosion1(self);
+}
+
+/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_floater (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_attack2 = gi.soundindex ("floater/fltatck2.wav");
+	sound_attack3 = gi.soundindex ("floater/fltatck3.wav");
+	sound_death1 = gi.soundindex ("floater/fltdeth1.wav");
+	sound_idle = gi.soundindex ("floater/fltidle1.wav");
+	sound_pain1 = gi.soundindex ("floater/fltpain1.wav");
+	sound_pain2 = gi.soundindex ("floater/fltpain2.wav");
+	sound_sight = gi.soundindex ("floater/fltsght1.wav");
+
+	gi.soundindex ("floater/fltatck1.wav");
+
+	self->s.sound = gi.soundindex ("floater/fltsrch1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/float/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+	self->health = 200;
+	self->gib_health = -80;
+	self->mass = 300;
+
+	self->pain = floater_pain;
+	self->die = floater_die;
+
+	self->monsterinfo.stand = floater_stand;
+	self->monsterinfo.walk = floater_walk;
+	self->monsterinfo.run = floater_run;
+//	self->monsterinfo.dodge = floater_dodge;
+	self->monsterinfo.attack = floater_attack;
+	self->monsterinfo.melee = floater_melee;
+	self->monsterinfo.sight = floater_sight;
+	self->monsterinfo.idle = floater_idle;
+
+	gi.linkentity (self);
+
+	if (qrandom() <= 0.5)		
+		self->monsterinfo.currentmove = &floater_move_stand1;	
+	else
+		self->monsterinfo.currentmove = &floater_move_stand2;	
+	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_float.h
@@ -1,0 +1,254 @@
+// G:\quake2\baseq2\models/monsters/float
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_actvat01        	0
+#define FRAME_actvat02        	1
+#define FRAME_actvat03        	2
+#define FRAME_actvat04        	3
+#define FRAME_actvat05        	4
+#define FRAME_actvat06        	5
+#define FRAME_actvat07        	6
+#define FRAME_actvat08        	7
+#define FRAME_actvat09        	8
+#define FRAME_actvat10        	9
+#define FRAME_actvat11        	10
+#define FRAME_actvat12        	11
+#define FRAME_actvat13        	12
+#define FRAME_actvat14        	13
+#define FRAME_actvat15        	14
+#define FRAME_actvat16        	15
+#define FRAME_actvat17        	16
+#define FRAME_actvat18        	17
+#define FRAME_actvat19        	18
+#define FRAME_actvat20        	19
+#define FRAME_actvat21        	20
+#define FRAME_actvat22        	21
+#define FRAME_actvat23        	22
+#define FRAME_actvat24        	23
+#define FRAME_actvat25        	24
+#define FRAME_actvat26        	25
+#define FRAME_actvat27        	26
+#define FRAME_actvat28        	27
+#define FRAME_actvat29        	28
+#define FRAME_actvat30        	29
+#define FRAME_actvat31        	30
+#define FRAME_attak101        	31
+#define FRAME_attak102        	32
+#define FRAME_attak103        	33
+#define FRAME_attak104        	34
+#define FRAME_attak105        	35
+#define FRAME_attak106        	36
+#define FRAME_attak107        	37
+#define FRAME_attak108        	38
+#define FRAME_attak109        	39
+#define FRAME_attak110        	40
+#define FRAME_attak111        	41
+#define FRAME_attak112        	42
+#define FRAME_attak113        	43
+#define FRAME_attak114        	44
+#define FRAME_attak201        	45
+#define FRAME_attak202        	46
+#define FRAME_attak203        	47
+#define FRAME_attak204        	48
+#define FRAME_attak205        	49
+#define FRAME_attak206        	50
+#define FRAME_attak207        	51
+#define FRAME_attak208        	52
+#define FRAME_attak209        	53
+#define FRAME_attak210        	54
+#define FRAME_attak211        	55
+#define FRAME_attak212        	56
+#define FRAME_attak213        	57
+#define FRAME_attak214        	58
+#define FRAME_attak215        	59
+#define FRAME_attak216        	60
+#define FRAME_attak217        	61
+#define FRAME_attak218        	62
+#define FRAME_attak219        	63
+#define FRAME_attak220        	64
+#define FRAME_attak221        	65
+#define FRAME_attak222        	66
+#define FRAME_attak223        	67
+#define FRAME_attak224        	68
+#define FRAME_attak225        	69
+#define FRAME_attak301        	70
+#define FRAME_attak302        	71
+#define FRAME_attak303        	72
+#define FRAME_attak304        	73
+#define FRAME_attak305        	74
+#define FRAME_attak306        	75
+#define FRAME_attak307        	76
+#define FRAME_attak308        	77
+#define FRAME_attak309        	78
+#define FRAME_attak310        	79
+#define FRAME_attak311        	80
+#define FRAME_attak312        	81
+#define FRAME_attak313        	82
+#define FRAME_attak314        	83
+#define FRAME_attak315        	84
+#define FRAME_attak316        	85
+#define FRAME_attak317        	86
+#define FRAME_attak318        	87
+#define FRAME_attak319        	88
+#define FRAME_attak320        	89
+#define FRAME_attak321        	90
+#define FRAME_attak322        	91
+#define FRAME_attak323        	92
+#define FRAME_attak324        	93
+#define FRAME_attak325        	94
+#define FRAME_attak326        	95
+#define FRAME_attak327        	96
+#define FRAME_attak328        	97
+#define FRAME_attak329        	98
+#define FRAME_attak330        	99
+#define FRAME_attak331        	100
+#define FRAME_attak332        	101
+#define FRAME_attak333        	102
+#define FRAME_attak334        	103
+#define FRAME_death01         	104
+#define FRAME_death02         	105
+#define FRAME_death03         	106
+#define FRAME_death04         	107
+#define FRAME_death05         	108
+#define FRAME_death06         	109
+#define FRAME_death07         	110
+#define FRAME_death08         	111
+#define FRAME_death09         	112
+#define FRAME_death10         	113
+#define FRAME_death11         	114
+#define FRAME_death12         	115
+#define FRAME_death13         	116
+#define FRAME_pain101         	117
+#define FRAME_pain102         	118
+#define FRAME_pain103         	119
+#define FRAME_pain104         	120
+#define FRAME_pain105         	121
+#define FRAME_pain106         	122
+#define FRAME_pain107         	123
+#define FRAME_pain201         	124
+#define FRAME_pain202         	125
+#define FRAME_pain203         	126
+#define FRAME_pain204         	127
+#define FRAME_pain205         	128
+#define FRAME_pain206         	129
+#define FRAME_pain207         	130
+#define FRAME_pain208         	131
+#define FRAME_pain301         	132
+#define FRAME_pain302         	133
+#define FRAME_pain303         	134
+#define FRAME_pain304         	135
+#define FRAME_pain305         	136
+#define FRAME_pain306         	137
+#define FRAME_pain307         	138
+#define FRAME_pain308         	139
+#define FRAME_pain309         	140
+#define FRAME_pain310         	141
+#define FRAME_pain311         	142
+#define FRAME_pain312         	143
+#define FRAME_stand101        	144
+#define FRAME_stand102        	145
+#define FRAME_stand103        	146
+#define FRAME_stand104        	147
+#define FRAME_stand105        	148
+#define FRAME_stand106        	149
+#define FRAME_stand107        	150
+#define FRAME_stand108        	151
+#define FRAME_stand109        	152
+#define FRAME_stand110        	153
+#define FRAME_stand111        	154
+#define FRAME_stand112        	155
+#define FRAME_stand113        	156
+#define FRAME_stand114        	157
+#define FRAME_stand115        	158
+#define FRAME_stand116        	159
+#define FRAME_stand117        	160
+#define FRAME_stand118        	161
+#define FRAME_stand119        	162
+#define FRAME_stand120        	163
+#define FRAME_stand121        	164
+#define FRAME_stand122        	165
+#define FRAME_stand123        	166
+#define FRAME_stand124        	167
+#define FRAME_stand125        	168
+#define FRAME_stand126        	169
+#define FRAME_stand127        	170
+#define FRAME_stand128        	171
+#define FRAME_stand129        	172
+#define FRAME_stand130        	173
+#define FRAME_stand131        	174
+#define FRAME_stand132        	175
+#define FRAME_stand133        	176
+#define FRAME_stand134        	177
+#define FRAME_stand135        	178
+#define FRAME_stand136        	179
+#define FRAME_stand137        	180
+#define FRAME_stand138        	181
+#define FRAME_stand139        	182
+#define FRAME_stand140        	183
+#define FRAME_stand141        	184
+#define FRAME_stand142        	185
+#define FRAME_stand143        	186
+#define FRAME_stand144        	187
+#define FRAME_stand145        	188
+#define FRAME_stand146        	189
+#define FRAME_stand147        	190
+#define FRAME_stand148        	191
+#define FRAME_stand149        	192
+#define FRAME_stand150        	193
+#define FRAME_stand151        	194
+#define FRAME_stand152        	195
+#define FRAME_stand201        	196
+#define FRAME_stand202        	197
+#define FRAME_stand203        	198
+#define FRAME_stand204        	199
+#define FRAME_stand205        	200
+#define FRAME_stand206        	201
+#define FRAME_stand207        	202
+#define FRAME_stand208        	203
+#define FRAME_stand209        	204
+#define FRAME_stand210        	205
+#define FRAME_stand211        	206
+#define FRAME_stand212        	207
+#define FRAME_stand213        	208
+#define FRAME_stand214        	209
+#define FRAME_stand215        	210
+#define FRAME_stand216        	211
+#define FRAME_stand217        	212
+#define FRAME_stand218        	213
+#define FRAME_stand219        	214
+#define FRAME_stand220        	215
+#define FRAME_stand221        	216
+#define FRAME_stand222        	217
+#define FRAME_stand223        	218
+#define FRAME_stand224        	219
+#define FRAME_stand225        	220
+#define FRAME_stand226        	221
+#define FRAME_stand227        	222
+#define FRAME_stand228        	223
+#define FRAME_stand229        	224
+#define FRAME_stand230        	225
+#define FRAME_stand231        	226
+#define FRAME_stand232        	227
+#define FRAME_stand233        	228
+#define FRAME_stand234        	229
+#define FRAME_stand235        	230
+#define FRAME_stand236        	231
+#define FRAME_stand237        	232
+#define FRAME_stand238        	233
+#define FRAME_stand239        	234
+#define FRAME_stand240        	235
+#define FRAME_stand241        	236
+#define FRAME_stand242        	237
+#define FRAME_stand243        	238
+#define FRAME_stand244        	239
+#define FRAME_stand245        	240
+#define FRAME_stand246        	241
+#define FRAME_stand247        	242
+#define FRAME_stand248        	243
+#define FRAME_stand249        	244
+#define FRAME_stand250        	245
+#define FRAME_stand251        	246
+#define FRAME_stand252        	247
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_flyer.c
@@ -1,0 +1,603 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_flyer.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	nextmove;			// Used for start/stop frames
+
+static int	sound_sight;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_slash;
+static int	sound_sproing;
+static int	sound_die;
+
+
+void flyer_check_melee(edict_t *self);
+void flyer_loop_melee (edict_t *self);
+void flyer_melee (edict_t *self);
+void flyer_setstart (edict_t *self);
+void flyer_stand (edict_t *self);
+void flyer_nextmove (edict_t *self);
+
+
+void flyer_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void flyer_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void flyer_pop_blades (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t flyer_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	flyer_move_stand = {FRAME_stand01, FRAME_stand45, flyer_frames_stand, NULL};
+
+
+mframe_t flyer_frames_walk [] =
+{
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 5, NULL
+};
+mmove_t	flyer_move_walk = {FRAME_stand01, FRAME_stand45, flyer_frames_walk, NULL};
+
+mframe_t flyer_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL,
+	ai_run, 10, NULL
+};
+mmove_t	flyer_move_run = {FRAME_stand01, FRAME_stand45, flyer_frames_run, NULL};
+
+void flyer_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &flyer_move_stand;
+	else
+		self->monsterinfo.currentmove = &flyer_move_run;
+}
+
+void flyer_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &flyer_move_walk;
+}
+
+void flyer_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flyer_move_stand;
+}
+
+mframe_t flyer_frames_start [] =
+{
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	flyer_nextmove
+};
+mmove_t flyer_move_start = {FRAME_start01, FRAME_start06, flyer_frames_start, NULL};
+
+mframe_t flyer_frames_stop [] =
+{
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	NULL,
+		ai_move, 0,	flyer_nextmove
+};
+mmove_t flyer_move_stop = {FRAME_stop01, FRAME_stop07, flyer_frames_stop, NULL};
+
+void flyer_stop (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flyer_move_stop;
+}
+
+void flyer_start (edict_t *self)
+{
+		self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+
+mframe_t flyer_frames_rollright [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_rollright = {FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, NULL};
+
+mframe_t flyer_frames_rollleft [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_rollleft = {FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, NULL};
+
+mframe_t flyer_frames_pain3 [] =
+{	
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain3 = {FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run};
+
+mframe_t flyer_frames_pain2 [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain2 = {FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run};
+
+mframe_t flyer_frames_pain1 [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_pain1 = {FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run};
+
+mframe_t flyer_frames_defense [] = 
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,		// Hold this frame
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_defense = {FRAME_defens01, FRAME_defens06, flyer_frames_defense, NULL};
+
+mframe_t flyer_frames_bankright [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_bankright = {FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, NULL};
+
+mframe_t flyer_frames_bankleft [] =
+{
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL,
+		ai_move, 0, NULL
+};
+mmove_t flyer_move_bankleft = {FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, NULL};		
+
+
+void flyer_fire (edict_t *self, int flash_number)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if ((self->s.frame == FRAME_attak204) || (self->s.frame == FRAME_attak207) || (self->s.frame == FRAME_attak210))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+	
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 1, 1000, flash_number, effect);
+}
+
+void flyer_fireleft (edict_t *self)
+{
+	flyer_fire (self, MZ2_FLYER_BLASTER_1);
+}
+
+void flyer_fireright (edict_t *self)
+{
+	flyer_fire (self, MZ2_FLYER_BLASTER_2);
+}
+
+
+mframe_t flyer_frames_attack2 [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, -10, flyer_fireleft,			// left gun
+		ai_charge, -10, flyer_fireright,		// right gun
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_attack2 = {FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run};
+
+
+void flyer_slash_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 0);
+	fire_hit (self, aim, 5, 0);
+	gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+void flyer_slash_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 0);
+	fire_hit (self, aim, 5, 0);
+	gi.sound (self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
+}
+
+mframe_t flyer_frames_start_melee [] =
+{
+		ai_charge, 0, flyer_pop_blades,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_start_melee = {FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee};
+
+mframe_t flyer_frames_end_melee [] =
+{
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL
+};
+mmove_t flyer_move_end_melee = {FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run};
+
+
+mframe_t flyer_frames_loop_melee [] =
+{
+		ai_charge, 0, NULL,		// Loop Start
+		ai_charge, 0, NULL,
+		ai_charge, 0, flyer_slash_left,		// Left Wing Strike
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, flyer_slash_right,	// Right Wing Strike
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL,
+		ai_charge, 0, NULL		// Loop Ends
+		
+};
+mmove_t flyer_move_loop_melee = {FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee};
+
+void flyer_loop_melee (edict_t *self)
+{
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	self->monsterinfo.currentmove = &flyer_move_loop_melee;
+}
+
+
+
+void flyer_attack (edict_t *self)
+{
+/*	if (random() <= 0.5)	
+		self->monsterinfo.currentmove = &flyer_move_attack1;
+	else */
+	self->monsterinfo.currentmove = &flyer_move_attack2;
+}
+
+void flyer_setstart (edict_t *self)
+{
+	nextmove = ACTION_run;
+	self->monsterinfo.currentmove = &flyer_move_start;
+}
+
+void flyer_nextmove (edict_t *self)
+{
+	if (nextmove == ACTION_attack1)
+		self->monsterinfo.currentmove = &flyer_move_start_melee;
+	else if (nextmove == ACTION_attack2)
+		self->monsterinfo.currentmove = &flyer_move_attack2;
+	else if (nextmove == ACTION_run)
+		self->monsterinfo.currentmove = &flyer_move_run;
+}
+
+void flyer_melee (edict_t *self)
+{
+//	flyer.nextmove = ACTION_attack1;
+//	self->monsterinfo.currentmove = &flyer_move_stop;
+	self->monsterinfo.currentmove = &flyer_move_start_melee;
+}
+
+void flyer_check_melee(edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+		if (qrandom() <= 0.8)
+			self->monsterinfo.currentmove = &flyer_move_loop_melee;
+		else
+			self->monsterinfo.currentmove = &flyer_move_end_melee;
+	else
+		self->monsterinfo.currentmove = &flyer_move_end_melee;
+}
+
+void flyer_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = rand() % 3;
+	if (n == 0)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain1;
+	}
+	else if (n == 1)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &flyer_move_pain3;
+	}
+}
+
+
+void flyer_die(edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	BecomeExplosion1(self);
+}
+	
+
+/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_flyer (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	// fix a map bug in jail5.bsp
+	if (!cistrcmp(level.mapname, "jail5") && (self->s.origin[2] == -104))
+	{
+		self->targetname = self->target;
+		self->target = NULL;
+	}
+
+	sound_sight = gi.soundindex ("flyer/flysght1.wav");
+	sound_idle = gi.soundindex ("flyer/flysrch1.wav");
+	sound_pain1 = gi.soundindex ("flyer/flypain1.wav");
+	sound_pain2 = gi.soundindex ("flyer/flypain2.wav");
+	sound_slash = gi.soundindex ("flyer/flyatck2.wav");
+	sound_sproing = gi.soundindex ("flyer/flyatck1.wav");
+	sound_die = gi.soundindex ("flyer/flydeth1.wav");
+
+	gi.soundindex ("flyer/flyatck3.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/flyer/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->s.sound = gi.soundindex ("flyer/flyidle1.wav");
+
+	self->health = 50;
+	self->mass = 50;
+
+	self->pain = flyer_pain;
+	self->die = flyer_die;
+
+	self->monsterinfo.stand = flyer_stand;
+	self->monsterinfo.walk = flyer_walk;
+	self->monsterinfo.run = flyer_run;
+	self->monsterinfo.attack = flyer_attack;
+	self->monsterinfo.melee = flyer_melee;
+	self->monsterinfo.sight = flyer_sight;
+	self->monsterinfo.idle = flyer_idle;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &flyer_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_flyer.h
@@ -1,0 +1,163 @@
+// G:\quake2\baseq2\models/monsters/flyer
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define ACTION_nothing			0
+#define ACTION_attack1			1
+#define ACTION_attack2			2
+#define ACTION_run				3
+#define ACTION_walk				4
+
+#define FRAME_start01         	0
+#define FRAME_start02         	1
+#define FRAME_start03         	2
+#define FRAME_start04         	3
+#define FRAME_start05         	4
+#define FRAME_start06         	5
+#define FRAME_stop01          	6
+#define FRAME_stop02          	7
+#define FRAME_stop03          	8
+#define FRAME_stop04          	9
+#define FRAME_stop05          	10
+#define FRAME_stop06          	11
+#define FRAME_stop07          	12
+#define FRAME_stand01         	13
+#define FRAME_stand02         	14
+#define FRAME_stand03         	15
+#define FRAME_stand04         	16
+#define FRAME_stand05         	17
+#define FRAME_stand06         	18
+#define FRAME_stand07         	19
+#define FRAME_stand08         	20
+#define FRAME_stand09         	21
+#define FRAME_stand10         	22
+#define FRAME_stand11         	23
+#define FRAME_stand12         	24
+#define FRAME_stand13         	25
+#define FRAME_stand14         	26
+#define FRAME_stand15         	27
+#define FRAME_stand16         	28
+#define FRAME_stand17         	29
+#define FRAME_stand18         	30
+#define FRAME_stand19         	31
+#define FRAME_stand20         	32
+#define FRAME_stand21         	33
+#define FRAME_stand22         	34
+#define FRAME_stand23         	35
+#define FRAME_stand24         	36
+#define FRAME_stand25         	37
+#define FRAME_stand26         	38
+#define FRAME_stand27         	39
+#define FRAME_stand28         	40
+#define FRAME_stand29         	41
+#define FRAME_stand30         	42
+#define FRAME_stand31         	43
+#define FRAME_stand32         	44
+#define FRAME_stand33         	45
+#define FRAME_stand34         	46
+#define FRAME_stand35         	47
+#define FRAME_stand36         	48
+#define FRAME_stand37         	49
+#define FRAME_stand38         	50
+#define FRAME_stand39         	51
+#define FRAME_stand40         	52
+#define FRAME_stand41         	53
+#define FRAME_stand42         	54
+#define FRAME_stand43         	55
+#define FRAME_stand44         	56
+#define FRAME_stand45         	57
+#define FRAME_attak101        	58
+#define FRAME_attak102        	59
+#define FRAME_attak103        	60
+#define FRAME_attak104        	61
+#define FRAME_attak105        	62
+#define FRAME_attak106        	63
+#define FRAME_attak107        	64
+#define FRAME_attak108        	65
+#define FRAME_attak109        	66
+#define FRAME_attak110        	67
+#define FRAME_attak111        	68
+#define FRAME_attak112        	69
+#define FRAME_attak113        	70
+#define FRAME_attak114        	71
+#define FRAME_attak115        	72
+#define FRAME_attak116        	73
+#define FRAME_attak117        	74
+#define FRAME_attak118        	75
+#define FRAME_attak119        	76
+#define FRAME_attak120        	77
+#define FRAME_attak121        	78
+#define FRAME_attak201        	79
+#define FRAME_attak202        	80
+#define FRAME_attak203        	81
+#define FRAME_attak204        	82
+#define FRAME_attak205        	83
+#define FRAME_attak206        	84
+#define FRAME_attak207        	85
+#define FRAME_attak208        	86
+#define FRAME_attak209        	87
+#define FRAME_attak210        	88
+#define FRAME_attak211        	89
+#define FRAME_attak212        	90
+#define FRAME_attak213        	91
+#define FRAME_attak214        	92
+#define FRAME_attak215        	93
+#define FRAME_attak216        	94
+#define FRAME_attak217        	95
+#define FRAME_bankl01         	96
+#define FRAME_bankl02         	97
+#define FRAME_bankl03         	98
+#define FRAME_bankl04         	99
+#define FRAME_bankl05         	100
+#define FRAME_bankl06         	101
+#define FRAME_bankl07         	102
+#define FRAME_bankr01         	103
+#define FRAME_bankr02         	104
+#define FRAME_bankr03         	105
+#define FRAME_bankr04         	106
+#define FRAME_bankr05         	107
+#define FRAME_bankr06         	108
+#define FRAME_bankr07         	109
+#define FRAME_rollf01         	110
+#define FRAME_rollf02         	111
+#define FRAME_rollf03         	112
+#define FRAME_rollf04         	113
+#define FRAME_rollf05         	114
+#define FRAME_rollf06         	115
+#define FRAME_rollf07         	116
+#define FRAME_rollf08         	117
+#define FRAME_rollf09         	118
+#define FRAME_rollr01         	119
+#define FRAME_rollr02         	120
+#define FRAME_rollr03         	121
+#define FRAME_rollr04         	122
+#define FRAME_rollr05         	123
+#define FRAME_rollr06         	124
+#define FRAME_rollr07         	125
+#define FRAME_rollr08         	126
+#define FRAME_rollr09         	127
+#define FRAME_defens01        	128
+#define FRAME_defens02        	129
+#define FRAME_defens03        	130
+#define FRAME_defens04        	131
+#define FRAME_defens05        	132
+#define FRAME_defens06        	133
+#define FRAME_pain101         	134
+#define FRAME_pain102         	135
+#define FRAME_pain103         	136
+#define FRAME_pain104         	137
+#define FRAME_pain105         	138
+#define FRAME_pain106         	139
+#define FRAME_pain107         	140
+#define FRAME_pain108         	141
+#define FRAME_pain109         	142
+#define FRAME_pain201         	143
+#define FRAME_pain202         	144
+#define FRAME_pain203         	145
+#define FRAME_pain204         	146
+#define FRAME_pain301         	147
+#define FRAME_pain302         	148
+#define FRAME_pain303         	149
+#define FRAME_pain304         	150
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_gekk.c
@@ -1,0 +1,1579 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gekk.h"
+
+static int	sound_swing;
+static int	sound_hit;
+static int	sound_hit2;
+static int	sound_death;
+static int	sound_pain1;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_step1;
+static int	sound_step2;
+static int	sound_step3;
+static int	sound_thud;
+static int  sound_chantlow;
+static int  sound_chantmid;
+static int  sound_chanthigh;
+
+void gekk_swim (edict_t *self);
+
+extern void gekk_jump_takeoff (edict_t *self);
+extern void gekk_jump_takeoff2 (edict_t *self);
+extern void gekk_check_landing (edict_t *self);
+extern void gekk_check_landing2 (edict_t *self);
+extern void gekk_stop_skid (edict_t *self);
+
+extern void water_to_land (edict_t *self);
+extern void land_to_water (edict_t *self);
+
+extern void gekk_check_underwater (edict_t *self);
+extern void gekk_bite (edict_t *self);
+
+extern void gekk_hit_left (edict_t *self);
+extern void gekk_hit_right (edict_t *self);
+extern void gekk_run_start (edict_t *self);
+
+extern mmove_t gekk_move_attack1;
+extern mmove_t gekk_move_attack2;
+extern mmove_t gekk_move_chant;
+extern mmove_t gekk_move_swim_start;
+extern mmove_t gekk_move_swim_loop;
+extern mmove_t gekk_move_spit;
+extern mmove_t gekk_move_run_start;
+
+extern qboolean gekk_check_jump (edict_t *self);
+
+//
+// CHECKATTACK
+//
+
+qboolean gekk_check_melee (edict_t *self)
+{
+	if (!self->enemy && self->enemy->health <= 0)
+		return false;
+
+	if (range (self, self->enemy) == RANGE_MELEE)
+		return true;
+	return false;
+}
+
+qboolean gekk_check_jump (edict_t *self)
+{
+	vec3_t	v;
+	float	distance;
+
+	if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+		return false;
+
+	if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+		return false;
+
+	v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+	v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+	v[2] = 0;
+	distance = VectorLength(v);
+
+	if (distance < 100)
+	{
+		return false;
+	}
+	if (distance > 100)
+	{
+		if (qrandom() < 0.9)
+			return false;
+	}
+
+	return true;
+}
+
+qboolean gekk_check_jump_close (edict_t *self)
+{
+	vec3_t	v;
+	float	distance;
+
+	v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+	v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+	v[2] = 0;
+	
+	distance = VectorLength(v);
+
+	if (distance < 100)
+	{
+		if (self->s.origin[2] < self->enemy->s.origin[2])
+			return true;
+		else
+			return false;
+	}
+	
+	return true;
+}
+
+
+qboolean gekk_checkattack (edict_t *self)
+{
+	if (!self->enemy || self->enemy->health <= 0)
+		return false;
+
+	if (gekk_check_melee(self))
+	{
+		self->monsterinfo.attack_state = AS_MELEE;
+		return true;
+	}
+
+	if (gekk_check_jump(self))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	if (gekk_check_jump_close (self) && !self->waterlevel)
+	{	
+		self->monsterinfo.attack_state = AS_MISSILE;
+		return true;
+	}
+
+	return false;
+}
+
+
+//
+// SOUNDS
+//
+
+void gekk_step (edict_t *self)
+{
+	int		n;
+	n = (rand() + 1) % 3;
+	if (n == 0)
+		gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);		
+	else if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+}
+
+void gekk_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gekk_search (edict_t *self)
+{
+	float r;
+
+	if (self->spawnflags & 8)
+	{
+		r = qrandom();
+		if (r < 0.33)
+			gi.sound (self, CHAN_VOICE, sound_chantlow, 1, ATTN_NORM, 0);
+		else if (r < 0.66)
+			gi.sound (self, CHAN_VOICE, sound_chantmid, 1, ATTN_NORM, 0);
+		else
+			gi.sound (self, CHAN_VOICE, sound_chanthigh, 1, ATTN_NORM, 0);
+	}
+	else
+		gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+	
+
+	self->health += 10 + (10 * qrandom());
+	if (self->health > self->max_health)
+		self->health = self->max_health;
+
+	if (self->health < (self->max_health /4))
+		self->s.skinnum = 2;
+	else if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+	else 
+		self->s.skinnum = 0;
+}
+
+void gekk_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+extern mmove_t gekk_move_run;
+void gekk_face (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gekk_move_run;	
+}
+
+//
+// STAND
+//
+
+void ai_stand2 (edict_t *self, float dist)
+{
+	if (self->spawnflags & 8)
+	{
+		ai_move (self, dist);
+		if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
+		{
+			if (self->monsterinfo.idle_time)
+			{
+				self->monsterinfo.idle (self);
+				self->monsterinfo.idle_time = level.time + 15 + qrandom() * 15;
+			}
+			else
+			{
+				self->monsterinfo.idle_time = level.time + qrandom() * 15;
+			}
+		}
+	}
+	else
+		ai_stand (self, dist);
+}
+
+mframe_t gekk_frames_stand [] =
+{
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		// 10
+
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		// 20
+
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		// 30
+
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	
+	ai_stand2, 0, gekk_check_underwater,
+};
+mmove_t gekk_move_stand = {FRAME_stand_01, FRAME_stand_39, gekk_frames_stand, NULL};
+
+mframe_t gekk_frames_standunderwater[] =
+{
+	ai_stand2, 0, NULL,	
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	
+	ai_stand2, 0, gekk_check_underwater
+};
+
+mmove_t gekk_move_standunderwater = {FRAME_amb_01, FRAME_amb_04, gekk_frames_standunderwater, NULL};
+
+
+void gekk_swim_loop (edict_t *self)
+{
+	self->flags |= FL_SWIM;	
+	self->monsterinfo.currentmove = &gekk_move_swim_loop;
+}
+
+mframe_t gekk_frames_swim [] =
+{
+	ai_run, 16, NULL,	
+	ai_run, 16, NULL,
+	ai_run, 16, NULL,
+	
+	ai_run, 16, gekk_swim
+};
+mmove_t gekk_move_swim_loop = {FRAME_amb_01, FRAME_amb_04, gekk_frames_swim, gekk_swim_loop};
+
+mframe_t gekk_frames_swim_start [] =
+{
+	ai_run, 14, NULL,
+	ai_run, 14, NULL,
+	ai_run, 14, NULL,
+	ai_run, 14, NULL,
+	ai_run, 16, NULL,
+	ai_run, 16, NULL,
+	ai_run, 16, NULL,
+	ai_run, 18, NULL,
+	ai_run, 18, gekk_hit_left,
+	ai_run, 18, NULL,
+	
+	ai_run, 20, NULL,
+	ai_run, 20, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 24, gekk_hit_right,
+	ai_run, 24, NULL,
+	ai_run, 26, NULL,
+	ai_run, 26, NULL,
+	ai_run, 24, NULL,
+	ai_run, 24, NULL,
+	
+	ai_run, 22, gekk_bite,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 22, NULL,
+	ai_run, 18, NULL,
+	ai_run, 18, NULL,
+
+	ai_run, 18, NULL,
+	ai_run, 18, NULL
+};
+mmove_t gekk_move_swim_start = {FRAME_swim_01, FRAME_swim_32, gekk_frames_swim_start, gekk_swim_loop};
+
+void gekk_swim (edict_t *self)
+{
+	
+	if (gekk_checkattack)
+		if (!self->enemy->waterlevel && qrandom() > 0.7)
+			water_to_land (self);
+	else
+		self->monsterinfo.currentmove = &gekk_move_swim_start;
+}
+
+
+void gekk_stand (edict_t *self)
+{
+	if (self->waterlevel)
+		self->monsterinfo.currentmove = &gekk_move_standunderwater;
+	else
+		self->monsterinfo.currentmove = &gekk_move_stand;
+}
+
+void gekk_chant (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gekk_move_chant;
+}
+
+//
+// IDLE
+//
+
+void gekk_idle_loop (edict_t *self)
+{
+	if (qrandom() > 0.75 && self->health < self->max_health)
+		self->monsterinfo.nextframe = FRAME_idle_01;
+}
+
+mframe_t gekk_frames_idle [] =
+{
+	ai_stand2, 0, gekk_search,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,		
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, NULL,
+	
+	ai_stand2, 0, NULL,
+	ai_stand2, 0, gekk_idle_loop
+};
+mmove_t gekk_move_idle = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_stand};
+mmove_t gekk_move_idle2 = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle, gekk_face};
+
+mframe_t gekk_frames_idle2 [] =
+{
+	ai_move, 0, gekk_search,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,		
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	
+	ai_move, 0, NULL,
+	ai_move, 0, gekk_idle_loop
+};
+mmove_t gekk_move_chant = {FRAME_idle_01, FRAME_idle_32, gekk_frames_idle2, gekk_chant};
+
+
+void gekk_idle (edict_t *self)
+{
+	if (!self->waterlevel)
+		self->monsterinfo.currentmove = &gekk_move_idle;
+	else
+		self->monsterinfo.currentmove = &gekk_move_swim_start;
+	// gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//
+// WALK
+//
+
+void gekk_walk (edict_t *self);
+mframe_t gekk_frames_walk[] = 
+{
+ ai_walk,   3.849, gekk_check_underwater, // frame 0
+ ai_walk,  19.606, NULL, // frame 1
+ ai_walk,  25.583, NULL, // frame 2
+ ai_walk,  34.625, gekk_step, // frame 3
+ ai_walk,  27.365, NULL, // frame 4
+ ai_walk,  28.480, NULL, // frame 5
+};
+
+mmove_t gekk_move_walk = {FRAME_run_01, FRAME_run_06, gekk_frames_walk, NULL};
+
+void gekk_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gekk_move_walk;
+}
+
+
+//
+// RUN
+//
+
+void gekk_run_start (edict_t *self)
+{
+	if (self->waterlevel)
+	{
+		self->monsterinfo.currentmove = &gekk_move_swim_start;
+	}	
+	else
+	{
+		self->monsterinfo.currentmove = &gekk_move_run_start;
+	}
+}
+
+void gekk_run (edict_t *self)
+{
+	
+	if (self->waterlevel)
+	{
+		self->monsterinfo.currentmove = &gekk_move_swim_start;
+		return;
+	}
+	else
+	{
+		if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+			self->monsterinfo.currentmove = &gekk_move_stand;
+		else
+			self->monsterinfo.currentmove = &gekk_move_run;
+	}
+
+}
+mframe_t gekk_frames_run[] = 
+{
+ ai_run,   3.849, gekk_check_underwater, // frame 0
+ ai_run,  19.606, NULL, // frame 1
+ ai_run,  25.583, NULL, // frame 2
+ ai_run,  34.625, gekk_step, // frame 3
+ ai_run,  27.365, NULL, // frame 4
+ ai_run,  28.480, NULL, // frame 5
+};
+mmove_t gekk_move_run = {FRAME_run_01, FRAME_run_06, gekk_frames_run, NULL};
+
+mframe_t gekk_frames_run_st[] = 
+{
+ ai_run,   0.212, NULL, // frame 0
+ ai_run,  19.753, NULL, // frame 1
+};
+mmove_t gekk_move_run_start = {FRAME_stand_01, FRAME_stand_02, gekk_frames_run_st, gekk_run};
+
+//
+// MELEE
+//
+
+void gekk_hit_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void gekk_hit_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8);
+	if (fire_hit (self, aim, (15 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void gekk_check_refire (edict_t *self)
+{
+	if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0)
+		return;
+	
+	if (qrandom() < (skill->value * 0.1))
+	{
+		if (range (self, self->enemy) == RANGE_MELEE)
+		{
+			if (self->s.frame == FRAME_clawatk3_09)
+				self->monsterinfo.currentmove = &gekk_move_attack2;
+			else if (self->s.frame == FRAME_clawatk5_09)
+				self->monsterinfo.currentmove = &gekk_move_attack1;
+		}
+	}
+	
+}
+
+
+void loogie_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
+{
+
+	if (other == self->owner)
+		return;
+
+	if (surf && (surf->flags & SURF_SKY))
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if (self->owner->client)
+		PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
+
+	if (other->takedamage)
+		T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, MOD_GEKK);
+	
+	G_FreeEdict (self);
+};
+	
+
+
+void fire_loogie (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed)
+{
+	edict_t	*loogie;
+	trace_t	tr;
+
+	VectorNormalize (dir);
+
+	loogie = G_Spawn();
+	VectorCopy (start, loogie->s.origin);
+	VectorCopy (start, loogie->s.old_origin);
+	vectoangles (dir, loogie->s.angles);
+	VectorScale (dir, speed, loogie->velocity);
+	loogie->movetype = MOVETYPE_FLYMISSILE;
+	loogie->clipmask = MASK_SHOT;
+	loogie->solid = SOLID_BBOX;
+	loogie->s.effects |= RF_FULLBRIGHT;
+	VectorClear (loogie->mins);
+	VectorClear (loogie->maxs);
+	
+	loogie->s.modelindex = gi.modelindex ("models/objects/loogy/tris.md2");
+	loogie->owner = self;
+	loogie->touch = loogie_touch;
+	loogie->nextthink = level.time + 2;
+	loogie->think = G_FreeEdict;
+	loogie->dmg = damage;
+	gi.linkentity (loogie);
+
+	tr = gi.trace (self->s.origin, NULL, NULL, loogie->s.origin, loogie, MASK_SHOT);
+	if (tr.fraction < 1.0)
+	{
+		VectorMA (loogie->s.origin, -10, dir, loogie->s.origin);
+		loogie->touch (loogie, tr.ent, NULL, NULL);
+	}
+
+}	
+
+void loogie (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	end;
+	vec3_t	dir;
+	vec3_t	gekkoffset;
+
+	VectorSet (gekkoffset, -18, -0.8, 24);
+
+	if (!self->enemy || self->enemy->health <= 0)
+		return;
+	
+	AngleVectors (self->s.angles, forward, right, up);
+	G_ProjectSource (self->s.origin, gekkoffset, forward, right, start);
+
+	VectorMA (start, 2, up, start);
+	
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	fire_loogie (self, start, dir, 5, 550);
+	
+}
+
+void reloogie (edict_t *self)
+{
+	if (qrandom() > 0.8 && self->health < self->max_health)
+	{
+		self->monsterinfo.currentmove = &gekk_move_idle2;
+		return;
+	}
+		
+	if (self->enemy->health >= 0)
+		if (qrandom() > 0.7 && (range(self, self->enemy) == RANGE_NEAR))
+			self->monsterinfo.currentmove = &gekk_move_spit;
+}
+
+
+mframe_t gekk_frames_spit [] =
+{
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ 
+ ai_charge,   0.000, loogie, 
+ ai_charge,   0.000, reloogie 
+};
+mmove_t gekk_move_spit = {FRAME_spit_01, FRAME_spit_07, gekk_frames_spit, gekk_run_start};
+
+
+mframe_t gekk_frames_attack1 [] =
+{
+	ai_charge,   0, NULL, 
+	ai_charge,   0, NULL, 
+	ai_charge,   0, NULL, 
+	
+	ai_charge,   0, gekk_hit_left, 
+	ai_charge,   0, NULL, 
+	ai_charge,   0, NULL, 
+	
+	ai_charge,   0, NULL, 
+	ai_charge,   0, NULL, 
+	ai_charge,   0, gekk_check_refire 
+		
+};
+mmove_t gekk_move_attack1 = {FRAME_clawatk3_01, FRAME_clawatk3_09, gekk_frames_attack1, gekk_run_start};
+
+mframe_t gekk_frames_attack2[] = 
+{
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, gekk_hit_left, 
+ 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, gekk_hit_right, 
+ 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, NULL, 
+ ai_charge,   0.000, gekk_check_refire 
+ 
+};
+mmove_t gekk_move_attack2 = {FRAME_clawatk5_01, FRAME_clawatk5_09, gekk_frames_attack2, gekk_run_start};
+
+void gekk_check_underwater (edict_t *self)
+{
+	if (self->waterlevel)
+	{
+		land_to_water (self);
+	}
+}
+
+mframe_t gekk_frames_leapatk[] = 
+{
+ ai_charge,   0.000, NULL, // frame 0
+ ai_charge,  -0.387, NULL, // frame 1
+ ai_charge,  -1.113, NULL, // frame 2
+ ai_charge,  -0.237, NULL, // frame 3
+ ai_charge,   6.720, gekk_jump_takeoff, // frame 4  last frame on ground
+ ai_charge,   6.414, NULL, // frame 5  leaves ground
+ ai_charge,   0.163, NULL, // frame 6
+ ai_charge,  28.316, NULL, // frame 7
+ ai_charge,  24.198, NULL, // frame 8
+ ai_charge,  31.742, NULL, // frame 9
+ ai_charge,  35.977, gekk_check_landing, // frame 10  last frame in air
+ ai_charge,  12.303, gekk_stop_skid, // frame 11  feet back on ground
+ ai_charge,  20.122, gekk_stop_skid, // frame 12
+ ai_charge,  -1.042, gekk_stop_skid, // frame 13
+ ai_charge,   2.556, gekk_stop_skid, // frame 14
+ ai_charge,   0.544, gekk_stop_skid, // frame 15
+ ai_charge,   1.862, gekk_stop_skid, // frame 16
+ ai_charge,   1.224, gekk_stop_skid, // frame 17
+
+ ai_charge,  -0.457, gekk_check_underwater, // frame 18
+};
+mmove_t gekk_move_leapatk = {FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk, gekk_run_start};
+
+
+mframe_t gekk_frames_leapatk2[] = 
+{
+ ai_charge,   0.000, NULL, // frame 0
+ ai_charge,  -0.387, NULL, // frame 1
+ ai_charge,  -1.113, NULL, // frame 2
+ ai_charge,  -0.237, NULL, // frame 3
+ ai_charge,   6.720, gekk_jump_takeoff2, // frame 4  last frame on ground
+ ai_charge,   6.414, NULL, // frame 5  leaves ground
+ ai_charge,   0.163, NULL, // frame 6
+ ai_charge,  28.316, NULL, // frame 7
+ ai_charge,  24.198, NULL, // frame 8
+ ai_charge,  31.742, NULL, // frame 9
+ ai_charge,  35.977, gekk_check_landing, // frame 10  last frame in air
+ ai_charge,  12.303, gekk_stop_skid, // frame 11  feet back on ground
+ ai_charge,  20.122, gekk_stop_skid, // frame 12
+ ai_charge,  -1.042, gekk_stop_skid, // frame 13
+ ai_charge,   2.556, gekk_stop_skid, // frame 14
+ ai_charge,   0.544, gekk_stop_skid, // frame 15
+ ai_charge,   1.862, gekk_stop_skid, // frame 16
+ ai_charge,   1.224, gekk_stop_skid, // frame 17
+ 
+ ai_charge,  -0.457, gekk_check_underwater, // frame 18
+};
+mmove_t gekk_move_leapatk2 = {FRAME_leapatk_01, FRAME_leapatk_19, gekk_frames_leapatk2, gekk_run_start};
+
+
+void gekk_bite (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	fire_hit (self, aim, 5, 0);
+}
+
+void gekk_preattack (edict_t *)
+{
+	// underwater attack sound
+	// gi.sound (self, CHAN_WEAPON, something something underwater sound, 1, ATTN_NORM, 0);
+	return;
+}
+
+
+mframe_t gekk_frames_attack [] =
+{
+	ai_charge, 16,	gekk_preattack,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	gekk_bite,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	gekk_bite,
+	
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	gekk_hit_left,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	NULL,
+	ai_charge, 16,	gekk_hit_right,
+	ai_charge, 16,	NULL,
+
+	ai_charge, 16,	NULL
+		
+};
+mmove_t gekk_move_attack = {FRAME_attack_01, FRAME_attack_21, gekk_frames_attack, gekk_run_start};
+
+void gekk_melee (edict_t *self)
+{
+	
+	float r;
+	
+	if (self->waterlevel)
+	{
+		self->monsterinfo.currentmove = &gekk_move_attack;	
+	}
+	else
+	{
+		r = qrandom();
+
+		if (r > 0.66)
+			self->monsterinfo.currentmove = &gekk_move_attack1;
+		else 
+			self->monsterinfo.currentmove = &gekk_move_attack2;
+		
+	}
+
+}
+
+
+//
+// ATTACK
+//
+
+void gekk_jump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->health <= 0)
+	{
+		self->touch = NULL;
+		return;
+	}
+
+	if (other->takedamage)
+	{
+		if (VectorLength(self->velocity) > 200)
+		{
+			vec3_t	point;
+			vec3_t	normal;
+			int		damage;
+
+			VectorCopy (self->velocity, normal);
+			VectorNormalize(normal);
+			VectorMA (self->s.origin, self->maxs[0], normal, point);
+			damage = 10 + 10 * qrandom();
+			T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_GEKK);
+		}
+	}
+
+	if (!M_CheckBottom (self))
+	{
+		if (self->groundentity)
+		{
+			self->monsterinfo.nextframe = FRAME_leapatk_11;
+			self->touch = NULL;
+		}
+		return;
+	}
+
+	self->touch = NULL;
+}
+
+void gekk_jump_takeoff (edict_t *self)
+{
+	vec3_t	forward;
+
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	self->s.origin[2] += 1;
+	
+	// high jump
+	if (gekk_check_jump (self))
+	{
+		VectorScale (forward, 700, self->velocity);
+		self->velocity[2] = 250;
+	}
+	else
+	{
+		VectorScale (forward, 250, self->velocity);
+		self->velocity[2] = 400;
+	}
+
+	
+	self->groundentity = NULL;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->monsterinfo.attack_finished = level.time + 3;
+	self->touch = gekk_jump_touch;
+}
+
+void gekk_jump_takeoff2 (edict_t *self)
+{
+	vec3_t	forward;
+
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	self->s.origin[2] = self->enemy->s.origin[2];
+	
+	if (gekk_check_jump (self))
+	{
+		VectorScale (forward, 300, self->velocity);
+		self->velocity[2] = 250;
+	}
+	else 
+	{
+		VectorScale (forward, 150, self->velocity);
+		self->velocity[2] = 300;
+	}
+
+	self->groundentity = NULL;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->monsterinfo.attack_finished = level.time + 3;
+	self->touch = gekk_jump_touch;
+	
+}
+
+void gekk_stop_skid (edict_t *self)
+{
+	if (self->groundentity)
+	{
+		VectorClear (self->velocity);
+	}
+}
+
+void gekk_check_landing (edict_t *self)
+{
+	if (self->groundentity)
+	{
+		gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+		self->monsterinfo.attack_finished = 0;
+		self->monsterinfo.aiflags &= ~AI_DUCKED;
+
+		VectorClear (self->velocity);
+
+		return;
+	}
+
+	// note to self
+	// causing skid
+	if (level.time > self->monsterinfo.attack_finished)
+		self->monsterinfo.nextframe = FRAME_leapatk_11;
+	else
+	{
+		self->monsterinfo.nextframe = FRAME_leapatk_12;
+	}
+}
+
+void gekk_jump (edict_t *self)
+{
+	
+	if (self->flags & FL_SWIM || self->waterlevel)
+	{
+		return;
+	}
+	else
+	{
+		//if (random() > 0.8 && self->health < self->max_health)
+		//	self->monsterinfo.currentmove = &gekk_move_idle2;
+		//else
+		{
+			if (qrandom() > 0.5 && (range (self, self->enemy) >= RANGE_NEAR))
+				self->monsterinfo.currentmove = &gekk_move_spit;
+			else if (qrandom() > 0.8)
+				self->monsterinfo.currentmove = &gekk_move_spit;
+			else
+				self->monsterinfo.currentmove = &gekk_move_leapatk;
+			
+		}
+	}
+}
+
+//
+// PAIN
+//
+
+mframe_t gekk_frames_pain[] = 
+{
+	ai_move,   0.000, NULL, // frame 0
+	ai_move,   0.000, NULL, // frame 1
+	ai_move,   0.000, NULL, // frame 2
+	ai_move,   0.000, NULL, // frame 3
+	ai_move,   0.000, NULL, // frame 4
+	ai_move,   0.000, NULL, // frame 5
+};
+mmove_t gekk_move_pain = {FRAME_pain_01, FRAME_pain_06, gekk_frames_pain, gekk_run_start};
+
+mframe_t gekk_frames_pain1[] = 
+{
+ ai_move,   0.000, NULL, // frame 0
+ ai_move,   0.000, NULL, // frame 1
+ ai_move,   0.000, NULL, // frame 2
+ ai_move,   0.000, NULL, // frame 3
+ ai_move,   0.000, NULL, // frame 4
+ ai_move,   0.000, NULL, // frame 5
+ ai_move,   0.000, NULL, // frame 6
+ ai_move,   0.000, NULL, // frame 7
+ ai_move,   0.000, NULL, // frame 8
+ ai_move,   0.000, NULL, // frame 9
+ 
+ ai_move,   0.000, gekk_check_underwater 
+};
+mmove_t gekk_move_pain1 = {FRAME_pain3_01, FRAME_pain3_11, gekk_frames_pain1, gekk_run_start};
+
+mframe_t gekk_frames_pain2[] = 
+{
+ ai_move,   0.000, NULL, // frame 0
+ ai_move,   0.000, NULL, // frame 1
+ ai_move,   0.000, NULL, // frame 2
+ ai_move,   0.000, NULL, // frame 3
+ ai_move,   0.000, NULL, // frame 4
+ ai_move,   0.000, NULL, // frame 5
+ ai_move,   0.000, NULL, // frame 6
+ ai_move,   0.000, NULL, // frame 7
+ ai_move,   0.000, NULL, // frame 8
+ ai_move,   0.000, NULL, // frame 9
+ 
+ ai_move,   0.000, NULL, // frame 10
+ ai_move,   0.000, NULL, // frame 11
+ ai_move,   0.000, gekk_check_underwater, 
+};
+mmove_t gekk_move_pain2 = {FRAME_pain4_01, FRAME_pain4_13, gekk_frames_pain2, gekk_run_start};
+
+void gekk_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+
+	if (self->spawnflags & 8)
+	{
+		self->spawnflags &= ~8;
+		return;
+	}
+	
+	if (self->health < (self->max_health /4))
+		self->s.skinnum = 2;
+	else if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		
+	if (self->waterlevel)
+	{
+		if (!self->flags & FL_SWIM)
+			self->flags |= FL_SWIM;
+		
+		self->monsterinfo.currentmove = &gekk_move_pain;
+	}
+	else
+	{
+		r = qrandom();
+
+		if (r > 0.5)
+			self->monsterinfo.currentmove = &gekk_move_pain1;
+		else
+			self->monsterinfo.currentmove = &gekk_move_pain2;
+	}
+}
+
+
+//
+// DEATH
+//
+
+void gekk_dead (edict_t *self)
+{
+	// fix this because of no blocking problem
+	if (self->waterlevel)
+	{
+		return;
+	}
+	else
+	{
+		VectorSet (self->mins, -16, -16, -24);
+		VectorSet (self->maxs, 16, 16, -8);
+		self->movetype = MOVETYPE_TOSS;
+		self->svflags |= SVF_DEADMONSTER;
+		self->nextthink = 0;
+		gi.linkentity (self);
+	}
+
+	
+}
+
+void gekk_gibfest (edict_t *self)
+{
+
+	int damage = 20;
+	
+	gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+	
+	ThrowGibACID (self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+	ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+	
+	ThrowHeadACID (self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC);
+	
+
+	self->deadflag = DEAD_DEAD;
+	
+}
+
+void isgibfest (edict_t *self)
+{
+	if (qrandom() > 0.9)
+		gekk_gibfest (self);
+}
+
+mframe_t gekk_frames_death1[] =
+{
+
+	ai_move,  -5.151, NULL, // frame 0
+	ai_move, -12.223, NULL, // frame 1
+	ai_move, -11.484, NULL, // frame 2
+	ai_move, -17.952, NULL, // frame 3
+	ai_move,  -6.953, NULL, // frame 4
+	ai_move,  -7.393, NULL, // frame 5
+	ai_move, -10.713, NULL, // frame 6
+	ai_move, -17.464, NULL, // frame 7
+	ai_move, -11.678, NULL, // frame 8
+	ai_move, -11.678, NULL  // frame 9
+};
+mmove_t gekk_move_death1 = {FRAME_death1_01, FRAME_death1_10, gekk_frames_death1, gekk_dead};
+
+mframe_t gekk_frames_death3[] =
+{
+	ai_move,   0.000, NULL, // frame 0
+	ai_move,   0.022, NULL, // frame 1
+	ai_move,   0.169, NULL, // frame 2
+	ai_move,  -0.710, NULL, // frame 3
+	ai_move, -13.446, NULL, // frame 4
+	ai_move,  -7.654, isgibfest, // frame 5
+	ai_move, -31.951, NULL, // frame 6
+	
+};
+mmove_t gekk_move_death3 = {FRAME_death3_01, FRAME_death3_07, gekk_frames_death3, gekk_dead};
+
+mframe_t gekk_frames_death4[] = 
+{
+ ai_move,   5.103, NULL, // frame 0
+ ai_move,  -4.808, NULL, // frame 1
+ ai_move, -10.509, NULL, // frame 2
+ ai_move,  -9.899, NULL, // frame 3
+ ai_move,   4.033, isgibfest, // frame 4
+ ai_move,  -5.197, NULL, // frame 5
+ ai_move,  -0.919, NULL, // frame 6
+ ai_move,  -8.821, NULL, // frame 7
+ ai_move,  -5.626, NULL, // frame 8
+ ai_move,  -8.865, isgibfest, // frame 9
+ ai_move,  -0.845, NULL, // frame 10
+ ai_move,   1.986, NULL, // frame 11
+ ai_move,   0.170, NULL, // frame 12
+ ai_move,   1.339, isgibfest, // frame 13
+ ai_move,  -0.922, NULL, // frame 14
+ ai_move,   0.818, NULL, // frame 15
+ ai_move,  -1.288, NULL, // frame 16
+ ai_move,  -1.408, isgibfest, // frame 17
+ ai_move,  -7.787, NULL, // frame 18
+ ai_move,  -3.995, NULL, // frame 19
+ ai_move,  -4.604, NULL, // frame 20
+ ai_move,  -1.715, isgibfest, // frame 21
+ ai_move,  -0.564, NULL, // frame 22
+ ai_move,  -0.597, NULL, // frame 23
+ ai_move,   0.074, NULL, // frame 24
+ ai_move,  -0.309, isgibfest, // frame 25
+ ai_move,  -0.395, NULL, // frame 26
+ ai_move,  -0.501, NULL, // frame 27
+ ai_move,  -0.325, NULL, // frame 28
+ ai_move,  -0.931, isgibfest, // frame 29
+ ai_move,  -1.433, NULL, // frame 30
+ ai_move,  -1.626, NULL, // frame 31
+ ai_move,   4.680, NULL, // frame 32
+ ai_move,   0.560, NULL, // frame 33
+ ai_move,  -0.549, gekk_gibfest // frame 34
+};
+mmove_t gekk_move_death4 = {FRAME_death4_01, FRAME_death4_35, gekk_frames_death4, gekk_dead};
+
+mframe_t gekk_frames_wdeath[] = 
+{
+	 ai_move,   0.000, NULL, // frame 0
+	 ai_move,   0.000, NULL, // frame 1
+	 ai_move,   0.000, NULL, // frame 2
+	 ai_move,   0.000, NULL, // frame 3
+	 ai_move,   0.000, NULL, // frame 4
+	 ai_move,   0.000, NULL, // frame 5
+	 ai_move,   0.000, NULL, // frame 6
+	 ai_move,   0.000, NULL, // frame 7
+	 ai_move,   0.000, NULL, // frame 8
+	 ai_move,   0.000, NULL, // frame 9
+	 ai_move,   0.000, NULL, // frame 10
+	 ai_move,   0.000, NULL, // frame 11
+	 ai_move,   0.000, NULL, // frame 12
+	 ai_move,   0.000, NULL, // frame 13
+	 ai_move,   0.000, NULL, // frame 14
+	 ai_move,   0.000, NULL, // frame 15
+	 ai_move,   0.000, NULL, // frame 16
+	 ai_move,   0.000, NULL, // frame 17
+	 ai_move,   0.000, NULL, // frame 18
+	 ai_move,   0.000, NULL, // frame 19
+	 ai_move,   0.000, NULL, // frame 20
+	 ai_move,   0.000, NULL, // frame 21
+	 ai_move,   0.000, NULL, // frame 22
+	 ai_move,   0.000, NULL, // frame 23
+	 ai_move,   0.000, NULL, // frame 24
+	 ai_move,   0.000, NULL, // frame 25
+	 ai_move,   0.000, NULL, // frame 26
+	 ai_move,   0.000, NULL, // frame 27
+	 ai_move,   0.000, NULL, // frame 28
+	 ai_move,   0.000, NULL, // frame 29
+	 ai_move,   0.000, NULL, // frame 30
+	 ai_move,   0.000, NULL, // frame 31
+	 ai_move,   0.000, NULL, // frame 32
+	 ai_move,   0.000, NULL, // frame 33
+	 ai_move,   0.000, NULL, // frame 34
+	 ai_move,   0.000, NULL, // frame 35
+	 ai_move,   0.000, NULL, // frame 36
+	 ai_move,   0.000, NULL, // frame 37
+	 ai_move,   0.000, NULL, // frame 38
+	 ai_move,   0.000, NULL, // frame 39
+	 ai_move,   0.000, NULL, // frame 40
+	 ai_move,   0.000, NULL, // frame 41
+	 ai_move,   0.000, NULL, // frame 42
+	 ai_move,   0.000, NULL, // frame 43
+	 ai_move,   0.000, NULL  // frame 44
+};
+mmove_t gekk_move_wdeath = {FRAME_wdeath_01, FRAME_wdeath_45, gekk_frames_wdeath, gekk_dead};
+
+void gekk_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	
+	float	r;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+
+		ThrowGibACID (self, "models/objects/gekkgib/pelvis/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/arm/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/torso/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/claw/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+		ThrowGibACID (self, "models/objects/gekkgib/leg/tris.md2", damage, GIB_ORGANIC);
+	
+		ThrowHeadACID (self, "models/objects/gekkgib/head/tris.md2", damage, GIB_ORGANIC);
+
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum = 2;
+
+	if (self->waterlevel)
+		self->monsterinfo.currentmove = &gekk_move_wdeath;
+	else
+	{
+		r = qrandom();
+		if (r > 0.66)
+			self->monsterinfo.currentmove = &gekk_move_death1;
+		else if (r > 0.33)
+			self->monsterinfo.currentmove = &gekk_move_death3;
+		else
+			self->monsterinfo.currentmove = &gekk_move_death4;
+
+	}
+	
+}
+
+
+/*
+	duck
+*/
+void gekk_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void gekk_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+
+void gekk_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t gekk_frames_lduck[] = 
+{
+ ai_move,   0.000, NULL, // frame 0
+ ai_move,   0.000, NULL, // frame 1
+ ai_move,   0.000, NULL, // frame 2
+ ai_move,   0.000, NULL, // frame 3
+ ai_move,   0.000, NULL, // frame 4
+ ai_move,   0.000, NULL, // frame 5
+ ai_move,   0.000, NULL, // frame 6
+ ai_move,   0.000, NULL, // frame 7
+ ai_move,   0.000, NULL, // frame 8
+ ai_move,   0.000, NULL, // frame 9
+ 
+ ai_move,   0.000, NULL, // frame 10
+ ai_move,   0.000, NULL, // frame 11
+ ai_move,   0.000, NULL  // frame 12
+ 
+};
+mmove_t gekk_move_lduck = {FRAME_lduck_01, FRAME_lduck_13, gekk_frames_lduck, gekk_run_start};
+
+mframe_t gekk_frames_rduck[] = 
+{
+ ai_move,   0.000, NULL, // frame 0
+ ai_move,   0.000, NULL, // frame 1
+ ai_move,   0.000, NULL, // frame 2
+ ai_move,   0.000, NULL, // frame 3
+ ai_move,   0.000, NULL, // frame 4
+ ai_move,   0.000, NULL, // frame 5
+ ai_move,   0.000, NULL, // frame 6
+ ai_move,   0.000, NULL, // frame 7
+ ai_move,   0.000, NULL, // frame 8
+ ai_move,   0.000, NULL, // frame 9
+ ai_move,   0.000, NULL, // frame 10
+ ai_move,   0.000, NULL, // frame 11
+ ai_move,   0.000, NULL // frame 12
+ 
+};
+mmove_t gekk_move_rduck = {FRAME_rduck_01, FRAME_rduck_13, gekk_frames_rduck, gekk_run_start};
+
+
+void gekk_dodge (edict_t *self, edict_t *attacker, float eta)
+{
+	float	r;
+
+	r = qrandom();
+	if (r > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	if (self->waterlevel)
+	{
+		self->monsterinfo.currentmove = &gekk_move_attack;
+		return;
+	}
+	
+	if (skill->value == 0)
+	{
+		r = qrandom();
+		if (r > 0.5)
+			self->monsterinfo.currentmove = &gekk_move_lduck;
+		else 
+			self->monsterinfo.currentmove = &gekk_move_rduck;
+		return;
+	}
+
+	self->monsterinfo.pausetime = level.time + eta + 0.3;
+	r = qrandom();
+
+	if (skill->value == 1)
+	{
+		if (r > 0.33)
+		{
+			r = qrandom();
+			if (r > 0.5)
+				self->monsterinfo.currentmove = &gekk_move_lduck;
+			else 
+				self->monsterinfo.currentmove = &gekk_move_rduck;
+		}
+		else
+		{
+			r = qrandom();
+			if (r > 0.66)
+				self->monsterinfo.currentmove = &gekk_move_attack1;
+			else 
+				self->monsterinfo.currentmove = &gekk_move_attack2;
+			
+		}
+		return;
+	}
+
+	if (skill->value == 2)
+	{
+		if (r > 0.66)
+		{
+			r = qrandom();
+			if (r > 0.5)
+				self->monsterinfo.currentmove = &gekk_move_lduck;
+			else 
+				self->monsterinfo.currentmove = &gekk_move_rduck;
+		}
+		else
+		{
+			r = qrandom();
+			if (r > 0.66)
+				self->monsterinfo.currentmove = &gekk_move_attack1;
+			else 
+				self->monsterinfo.currentmove = &gekk_move_attack2;
+		}
+		return;
+	}
+
+	r = qrandom();
+	if (r > 0.66)
+		self->monsterinfo.currentmove = &gekk_move_attack1;
+	else 
+		self->monsterinfo.currentmove = &gekk_move_attack2;
+	
+	
+}
+
+//
+// SPAWN
+//
+
+/*QUAKED monster_gekk (1 .5 0) (-24 -24 -24) (24 24 24) Ambush Trigger_Spawn Sight Chant
+*/
+void SP_monster_gekk (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_swing = gi.soundindex ("gek/gk_atck1.wav");
+	sound_hit = gi.soundindex ("gek/gk_atck2.wav");
+	sound_hit2 = gi.soundindex ("gek/gk_atck3.wav");
+	sound_death = gi.soundindex ("gek/gk_deth1.wav");
+	sound_pain1 = gi.soundindex ("gek/gk_pain1.wav");
+	sound_sight = gi.soundindex ("gek/gk_sght1.wav");
+	sound_search = gi.soundindex ("gek/gk_idle1.wav");
+	sound_step1 = gi.soundindex ("gek/gk_step1.wav");
+	sound_step2 = gi.soundindex ("gek/gk_step2.wav");
+	sound_step3 = gi.soundindex ("gek/gk_step3.wav");
+	sound_thud = gi.soundindex ("mutant/thud1.wav");
+	
+	sound_chantlow = gi.soundindex ("gek/gek_low.wav");
+	sound_chantmid = gi.soundindex ("gek/gek_mid.wav");
+	sound_chanthigh = gi.soundindex ("gek/gek_high.wav");
+	
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gekk/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 24);
+
+	gi.modelindex ("models/objects/gekkgib/pelvis/tris.md2");
+	gi.modelindex ("models/objects/gekkgib/arm/tris.md2");
+	gi.modelindex ("models/objects/gekkgib/torso/tris.md2");
+	gi.modelindex ("models/objects/gekkgib/claw/tris.md2");
+	gi.modelindex ("models/objects/gekkgib/leg/tris.md2");
+	gi.modelindex ("models/objects/gekkgib/head/tris.md2");
+		
+	self->health = 125;
+	self->gib_health = -30;
+	self->mass = 300;
+
+	self->pain = gekk_pain;
+	self->die = gekk_die;
+
+	self->monsterinfo.stand = gekk_stand;
+
+	self->monsterinfo.walk = gekk_walk;
+	self->monsterinfo.run = gekk_run_start;
+	self->monsterinfo.dodge = gekk_dodge;
+	self->monsterinfo.attack = gekk_jump;
+	self->monsterinfo.melee = gekk_melee;
+	self->monsterinfo.sight = gekk_sight;
+	
+	self->monsterinfo.search = gekk_search;
+	self->monsterinfo.idle = gekk_idle;
+	self->monsterinfo.checkattack = gekk_checkattack;
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &gekk_move_stand;
+
+	self->monsterinfo.scale = MODEL_SCALE;
+	walkmonster_start (self);
+
+	if (self->spawnflags & 8)
+		self->monsterinfo.currentmove = &gekk_move_chant;
+	
+}
+
+
+void water_to_land (edict_t *self)
+{
+	self->flags &= ~FL_SWIM;
+	self->yaw_speed = 20;
+	self->viewheight = 25;
+	
+	self->monsterinfo.currentmove = &gekk_move_leapatk2;
+	
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 24);
+}
+
+void land_to_water (edict_t *self)
+{
+	self->flags |= FL_SWIM;
+	self->yaw_speed = 10;
+	self->viewheight = 10;
+	
+	self->monsterinfo.currentmove = &gekk_move_swim_start;
+	
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 16);
+}
--- /dev/null
+++ b/xatrix/m_gekk.h
@@ -1,0 +1,356 @@
+// ./gekk
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand_01		0
+#define FRAME_stand_02		1
+#define FRAME_stand_03		2
+#define FRAME_stand_04		3
+#define FRAME_stand_05		4
+#define FRAME_stand_06		5
+#define FRAME_stand_07		6
+#define FRAME_stand_08		7
+#define FRAME_stand_09		8
+#define FRAME_stand_10		9
+#define FRAME_stand_11		10
+#define FRAME_stand_12		11
+#define FRAME_stand_13		12
+#define FRAME_stand_14		13
+#define FRAME_stand_15		14
+#define FRAME_stand_16		15
+#define FRAME_stand_17		16
+#define FRAME_stand_18		17
+#define FRAME_stand_19		18
+#define FRAME_stand_20		19
+#define FRAME_stand_21		20
+#define FRAME_stand_22		21
+#define FRAME_stand_23		22
+#define FRAME_stand_24		23
+#define FRAME_stand_25		24
+#define FRAME_stand_26		25
+#define FRAME_stand_27		26
+#define FRAME_stand_28		27
+#define FRAME_stand_29		28
+#define FRAME_stand_30		29
+#define FRAME_stand_31		30
+#define FRAME_stand_32		31
+#define FRAME_stand_33		32
+#define FRAME_stand_34		33
+#define FRAME_stand_35		34
+#define FRAME_stand_36		35
+#define FRAME_stand_37		36
+#define FRAME_stand_38		37
+#define FRAME_stand_39		38
+#define FRAME_run_01		39
+#define FRAME_run_02		40
+#define FRAME_run_03		41
+#define FRAME_run_04		42
+#define FRAME_run_05		43
+#define FRAME_run_06		44
+#define FRAME_clawatk3_01		45
+#define FRAME_clawatk3_02		46
+#define FRAME_clawatk3_03		47
+#define FRAME_clawatk3_04		48
+#define FRAME_clawatk3_05		49
+#define FRAME_clawatk3_06		50
+#define FRAME_clawatk3_07		51
+#define FRAME_clawatk3_08		52
+#define FRAME_clawatk3_09		53
+#define FRAME_clawatk4_01		54
+#define FRAME_clawatk4_02		55
+#define FRAME_clawatk4_03		56
+#define FRAME_clawatk4_04		57
+#define FRAME_clawatk4_05		58
+#define FRAME_clawatk4_06		59
+#define FRAME_clawatk4_07		60
+#define FRAME_clawatk4_08		61
+#define FRAME_clawatk5_01		62
+#define FRAME_clawatk5_02		63
+#define FRAME_clawatk5_03		64
+#define FRAME_clawatk5_04		65
+#define FRAME_clawatk5_05		66
+#define FRAME_clawatk5_06		67
+#define FRAME_clawatk5_07		68
+#define FRAME_clawatk5_08		69
+#define FRAME_clawatk5_09		70
+#define FRAME_leapatk_01		71
+#define FRAME_leapatk_02		72
+#define FRAME_leapatk_03		73
+#define FRAME_leapatk_04		74
+#define FRAME_leapatk_05		75
+#define FRAME_leapatk_06		76
+#define FRAME_leapatk_07		77
+#define FRAME_leapatk_08		78
+#define FRAME_leapatk_09		79
+#define FRAME_leapatk_10		80
+#define FRAME_leapatk_11		81
+#define FRAME_leapatk_12		82
+#define FRAME_leapatk_13		83
+#define FRAME_leapatk_14		84
+#define FRAME_leapatk_15		85
+#define FRAME_leapatk_16		86
+#define FRAME_leapatk_17		87
+#define FRAME_leapatk_18		88
+#define FRAME_leapatk_19		89
+#define FRAME_pain3_01		90
+#define FRAME_pain3_02		91
+#define FRAME_pain3_03		92
+#define FRAME_pain3_04		93
+#define FRAME_pain3_05		94
+#define FRAME_pain3_06		95
+#define FRAME_pain3_07		96
+#define FRAME_pain3_08		97
+#define FRAME_pain3_09		98
+#define FRAME_pain3_10		99
+#define FRAME_pain3_11		100
+#define FRAME_pain4_01		101
+#define FRAME_pain4_02		102
+#define FRAME_pain4_03		103
+#define FRAME_pain4_04		104
+#define FRAME_pain4_05		105
+#define FRAME_pain4_06		106
+#define FRAME_pain4_07		107
+#define FRAME_pain4_08		108
+#define FRAME_pain4_09		109
+#define FRAME_pain4_10		110
+#define FRAME_pain4_11		111
+#define FRAME_pain4_12		112
+#define FRAME_pain4_13		113
+#define FRAME_death1_01		114
+#define FRAME_death1_02		115
+#define FRAME_death1_03		116
+#define FRAME_death1_04		117
+#define FRAME_death1_05		118
+#define FRAME_death1_06		119
+#define FRAME_death1_07		120
+#define FRAME_death1_08		121
+#define FRAME_death1_09		122
+#define FRAME_death1_10		123
+#define FRAME_death2_01		124
+#define FRAME_death2_02		125
+#define FRAME_death2_03		126
+#define FRAME_death2_04		127
+#define FRAME_death2_05		128
+#define FRAME_death2_06		129
+#define FRAME_death2_07		130
+#define FRAME_death2_08		131
+#define FRAME_death2_09		132
+#define FRAME_death2_10		133
+#define FRAME_death2_11		134
+#define FRAME_death3_01		135
+#define FRAME_death3_02		136
+#define FRAME_death3_03		137
+#define FRAME_death3_04		138
+#define FRAME_death3_05		139
+#define FRAME_death3_06		140
+#define FRAME_death3_07		141
+#define FRAME_death4_01		142
+#define FRAME_death4_02		143
+#define FRAME_death4_03		144
+#define FRAME_death4_04		145
+#define FRAME_death4_05		146
+#define FRAME_death4_06		147
+#define FRAME_death4_07		148
+#define FRAME_death4_08		149
+#define FRAME_death4_09		150
+#define FRAME_death4_10		151
+#define FRAME_death4_11		152
+#define FRAME_death4_12		153
+#define FRAME_death4_13		154
+#define FRAME_death4_14		155
+#define FRAME_death4_15		156
+#define FRAME_death4_16		157
+#define FRAME_death4_17		158
+#define FRAME_death4_18		159
+#define FRAME_death4_19		160
+#define FRAME_death4_20		161
+#define FRAME_death4_21		162
+#define FRAME_death4_22		163
+#define FRAME_death4_23		164
+#define FRAME_death4_24		165
+#define FRAME_death4_25		166
+#define FRAME_death4_26		167
+#define FRAME_death4_27		168
+#define FRAME_death4_28		169
+#define FRAME_death4_29		170
+#define FRAME_death4_30		171
+#define FRAME_death4_31		172
+#define FRAME_death4_32		173
+#define FRAME_death4_33		174
+#define FRAME_death4_34		175
+#define FRAME_death4_35		176
+#define FRAME_rduck_01		177
+#define FRAME_rduck_02		178
+#define FRAME_rduck_03		179
+#define FRAME_rduck_04		180
+#define FRAME_rduck_05		181
+#define FRAME_rduck_06		182
+#define FRAME_rduck_07		183
+#define FRAME_rduck_08		184
+#define FRAME_rduck_09		185
+#define FRAME_rduck_10		186
+#define FRAME_rduck_11		187
+#define FRAME_rduck_12		188
+#define FRAME_rduck_13		189
+#define FRAME_lduck_01		190
+#define FRAME_lduck_02		191
+#define FRAME_lduck_03		192
+#define FRAME_lduck_04		193
+#define FRAME_lduck_05		194
+#define FRAME_lduck_06		195
+#define FRAME_lduck_07		196
+#define FRAME_lduck_08		197
+#define FRAME_lduck_09		198
+#define FRAME_lduck_10		199
+#define FRAME_lduck_11		200
+#define FRAME_lduck_12		201
+#define FRAME_lduck_13		202
+#define FRAME_idle_01		203
+#define FRAME_idle_02		204
+#define FRAME_idle_03		205
+#define FRAME_idle_04		206
+#define FRAME_idle_05		207
+#define FRAME_idle_06		208
+#define FRAME_idle_07		209
+#define FRAME_idle_08		210
+#define FRAME_idle_09		211
+#define FRAME_idle_10		212
+#define FRAME_idle_11		213
+#define FRAME_idle_12		214
+#define FRAME_idle_13		215
+#define FRAME_idle_14		216
+#define FRAME_idle_15		217
+#define FRAME_idle_16		218
+#define FRAME_idle_17		219
+#define FRAME_idle_18		220
+#define FRAME_idle_19		221
+#define FRAME_idle_20		222
+#define FRAME_idle_21		223
+#define FRAME_idle_22		224
+#define FRAME_idle_23		225
+#define FRAME_idle_24		226
+#define FRAME_idle_25		227
+#define FRAME_idle_26		228
+#define FRAME_idle_27		229
+#define FRAME_idle_28		230
+#define FRAME_idle_29		231
+#define FRAME_idle_30		232
+#define FRAME_idle_31		233
+#define FRAME_idle_32		234
+#define FRAME_spit_01		235
+#define FRAME_spit_02		236
+#define FRAME_spit_03		237
+#define FRAME_spit_04		238
+#define FRAME_spit_05		239
+#define FRAME_spit_06		240
+#define FRAME_spit_07		241
+#define FRAME_amb_01		242
+#define FRAME_amb_02		243
+#define FRAME_amb_03		244
+#define FRAME_amb_04		245
+#define FRAME_wdeath_01		246
+#define FRAME_wdeath_02		247
+#define FRAME_wdeath_03		248
+#define FRAME_wdeath_04		249
+#define FRAME_wdeath_05		250
+#define FRAME_wdeath_06		251
+#define FRAME_wdeath_07		252
+#define FRAME_wdeath_08		253
+#define FRAME_wdeath_09		254
+#define FRAME_wdeath_10		255
+#define FRAME_wdeath_11		256
+#define FRAME_wdeath_12		257
+#define FRAME_wdeath_13		258
+#define FRAME_wdeath_14		259
+#define FRAME_wdeath_15		260
+#define FRAME_wdeath_16		261
+#define FRAME_wdeath_17		262
+#define FRAME_wdeath_18		263
+#define FRAME_wdeath_19		264
+#define FRAME_wdeath_20		265
+#define FRAME_wdeath_21		266
+#define FRAME_wdeath_22		267
+#define FRAME_wdeath_23		268
+#define FRAME_wdeath_24		269
+#define FRAME_wdeath_25		270
+#define FRAME_wdeath_26		271
+#define FRAME_wdeath_27		272
+#define FRAME_wdeath_28		273
+#define FRAME_wdeath_29		274
+#define FRAME_wdeath_30		275
+#define FRAME_wdeath_31		276
+#define FRAME_wdeath_32		277
+#define FRAME_wdeath_33		278
+#define FRAME_wdeath_34		279
+#define FRAME_wdeath_35		280
+#define FRAME_wdeath_36		281
+#define FRAME_wdeath_37		282
+#define FRAME_wdeath_38		283
+#define FRAME_wdeath_39		284
+#define FRAME_wdeath_40		285
+#define FRAME_wdeath_41		286
+#define FRAME_wdeath_42		287
+#define FRAME_wdeath_43		288
+#define FRAME_wdeath_44		289
+#define FRAME_wdeath_45		290
+#define FRAME_swim_01		291
+#define FRAME_swim_02		292
+#define FRAME_swim_03		293
+#define FRAME_swim_04		294
+#define FRAME_swim_05		295
+#define FRAME_swim_06		296
+#define FRAME_swim_07		297
+#define FRAME_swim_08		298
+#define FRAME_swim_09		299
+#define FRAME_swim_10		300
+#define FRAME_swim_11		301
+#define FRAME_swim_12		302
+#define FRAME_swim_13		303
+#define FRAME_swim_14		304
+#define FRAME_swim_15		305
+#define FRAME_swim_16		306
+#define FRAME_swim_17		307
+#define FRAME_swim_18		308
+#define FRAME_swim_19		309
+#define FRAME_swim_20		310
+#define FRAME_swim_21		311
+#define FRAME_swim_22		312
+#define FRAME_swim_23		313
+#define FRAME_swim_24		314
+#define FRAME_swim_25		315
+#define FRAME_swim_26		316
+#define FRAME_swim_27		317
+#define FRAME_swim_28		318
+#define FRAME_swim_29		319
+#define FRAME_swim_30		320
+#define FRAME_swim_31		321
+#define FRAME_swim_32		322
+#define FRAME_attack_01		323
+#define FRAME_attack_02		324
+#define FRAME_attack_03		325
+#define FRAME_attack_04		326
+#define FRAME_attack_05		327
+#define FRAME_attack_06		328
+#define FRAME_attack_07		329
+#define FRAME_attack_08		330
+#define FRAME_attack_09		331
+#define FRAME_attack_10		332
+#define FRAME_attack_11		333
+#define FRAME_attack_12		334
+#define FRAME_attack_13		335
+#define FRAME_attack_14		336
+#define FRAME_attack_15		337
+#define FRAME_attack_16		338
+#define FRAME_attack_17		339
+#define FRAME_attack_18		340
+#define FRAME_attack_19		341
+#define FRAME_attack_20		342
+#define FRAME_attack_21		343
+#define FRAME_pain_01		344
+#define FRAME_pain_02		345
+#define FRAME_pain_03		346
+#define FRAME_pain_04		347
+#define FRAME_pain_05		348
+#define FRAME_pain_06		349
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_gladb.c
@@ -1,0 +1,375 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gladiator.h"
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_gun;
+static int	sound_cleaver_swing;
+static int	sound_cleaver_hit;
+static int	sound_cleaver_miss;
+static int	sound_idle;
+static int	sound_search;
+static int	sound_sight;
+
+
+void gladb_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void gladb_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gladb_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void gladb_cleaver_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladb_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t gladb_move_stand = {FRAME_stand1, FRAME_stand7, gladb_frames_stand, NULL};
+
+void gladb_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladb_move_stand;
+}
+
+
+mframe_t gladb_frames_walk [] =
+{
+	ai_walk, 15, NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 12, NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 8,  NULL
+};
+mmove_t gladb_move_walk = {FRAME_walk1, FRAME_walk16, gladb_frames_walk, NULL};
+
+void gladb_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladb_move_walk;
+}
+
+
+mframe_t gladb_frames_run [] =
+{
+	ai_run, 23,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 21,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 13,	NULL
+};
+mmove_t gladb_move_run = {FRAME_run1, FRAME_run6, gladb_frames_run, NULL};
+
+void gladb_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &gladb_move_stand;
+	else
+		self->monsterinfo.currentmove = &gladb_move_run;
+}
+
+
+void GladbMelee (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4);
+	if (fire_hit (self, aim, (20 + (rand() %5)), 300))
+		gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladb_frames_attack_melee [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladb_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GladbMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladb_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GladbMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gladb_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladb_frames_attack_melee, gladb_run};
+
+void gladb_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladb_move_attack_melee;
+}
+
+
+void gladbGun (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start);
+
+	// calc direction to where we targted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	fire_plasma (self, start, dir, 100, 725, 60, 60);
+}
+
+void gladbGun_check (edict_t *self)
+{
+	if (skill->value == 3)
+		gladbGun (self);
+}
+
+mframe_t gladb_frames_attack_gun [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladbGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladbGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladbGun_check
+};
+mmove_t gladb_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladb_run};
+
+void gladb_attack(edict_t *self)
+{
+	float	range;
+	vec3_t	v;
+
+	// a small safe zone
+	VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+	range = VectorLength(v);
+	if (range <= (MELEE_DISTANCE + 32))
+		return;
+
+	// charge up the railgun
+	gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+	self->monsterinfo.currentmove = &gladb_move_attack_gun;
+}
+
+
+mframe_t gladb_frames_pain [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladb_move_pain = {FRAME_pain1, FRAME_pain6, gladb_frames_pain, gladb_run};
+
+mframe_t gladb_frames_pain_air [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladb_move_pain_air = {FRAME_painup1, FRAME_painup7, gladb_frames_pain_air, gladb_run};
+
+void gladb_pain (edict_t *self, edict_t *, float, int)
+{
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladb_move_pain))
+			self->monsterinfo.currentmove = &gladb_move_pain_air;
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	if (self->velocity[2] > 100)
+		self->monsterinfo.currentmove = &gladb_move_pain_air;
+	else
+		self->monsterinfo.currentmove = &gladb_move_pain;
+	
+}
+
+
+void gladb_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t gladb_frames_death [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladb_move_death = {FRAME_death1, FRAME_death22, gladb_frames_death, gladb_dead};
+
+void gladb_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &gladb_move_death;
+}
+
+
+/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_gladb (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+
+	sound_pain1 = gi.soundindex ("gladiator/pain.wav");	
+	sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav");	
+	sound_die = gi.soundindex ("gladiator/glddeth2.wav");	
+	// note to self
+	// need to change to PHALANX sound
+	sound_gun = gi.soundindex ("weapons/plasshot.wav");
+
+	sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav");
+	sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav");
+	sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav");
+	sound_idle = gi.soundindex ("gladiator/gldidle1.wav");
+	sound_search = gi.soundindex ("gladiator/gldsrch1.wav");
+	sound_sight = gi.soundindex ("gladiator/sight.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gladb/tris.md2");
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 64);
+
+	self->health = 800;
+	self->gib_health = -175;
+	self->mass = 350;
+
+	self->pain = gladb_pain;
+	self->die = gladb_die;
+
+	self->monsterinfo.stand = gladb_stand;
+	self->monsterinfo.walk = gladb_walk;
+	self->monsterinfo.run = gladb_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = gladb_attack;
+	self->monsterinfo.melee = gladb_melee;
+	self->monsterinfo.sight = gladb_sight;
+	self->monsterinfo.idle = gladb_idle;
+	self->monsterinfo.search = gladb_search;
+
+	gi.linkentity (self);
+	self->monsterinfo.currentmove = &gladb_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+	self->monsterinfo.power_armor_power = 400;
+
+
+	walkmonster_start (self);
+}
+
--- /dev/null
+++ b/xatrix/m_gladiator.c
@@ -1,0 +1,365 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gladiator.h"
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_gun;
+static int	sound_cleaver_swing;
+static int	sound_cleaver_hit;
+static int	sound_cleaver_miss;
+static int	sound_idle;
+static int	sound_search;
+static int	sound_sight;
+
+
+void gladiator_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void gladiator_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gladiator_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void gladiator_cleaver_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t gladiator_move_stand = {FRAME_stand1, FRAME_stand7, gladiator_frames_stand, NULL};
+
+void gladiator_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_stand;
+}
+
+
+mframe_t gladiator_frames_walk [] =
+{
+	ai_walk, 15, NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 12, NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 8,  NULL
+};
+mmove_t gladiator_move_walk = {FRAME_walk1, FRAME_walk16, gladiator_frames_walk, NULL};
+
+void gladiator_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_walk;
+}
+
+
+mframe_t gladiator_frames_run [] =
+{
+	ai_run, 23,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 14,	NULL,
+	ai_run, 21,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 13,	NULL
+};
+mmove_t gladiator_move_run = {FRAME_run1, FRAME_run6, gladiator_frames_run, NULL};
+
+void gladiator_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &gladiator_move_stand;
+	else
+		self->monsterinfo.currentmove = &gladiator_move_run;
+}
+
+
+void GaldiatorMelee (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], -4);
+	if (fire_hit (self, aim, (20 + (rand() %5)), 300))
+		gi.sound (self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
+}
+
+mframe_t gladiator_frames_attack_melee [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladiator_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GaldiatorMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, gladiator_cleaver_swing,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GaldiatorMelee,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gladiator_move_attack_melee = {FRAME_melee1, FRAME_melee17, gladiator_frames_attack_melee, gladiator_run};
+
+void gladiator_melee(edict_t *self)
+{
+	self->monsterinfo.currentmove = &gladiator_move_attack_melee;
+}
+
+
+void GladiatorGun (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	forward, right;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right, start);
+
+	// calc direction to where we targted
+	VectorSubtract (self->pos1, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_railgun (self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1);
+}
+
+mframe_t gladiator_frames_attack_gun [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GladiatorGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gladiator_move_attack_gun = {FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run};
+
+void gladiator_attack(edict_t *self)
+{
+	float	range;
+	vec3_t	v;
+
+	// a small safe zone
+	VectorSubtract (self->s.origin, self->enemy->s.origin, v);
+	range = VectorLength(v);
+	if (range <= (MELEE_DISTANCE + 32))
+		return;
+
+	// charge up the railgun
+	gi.sound (self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
+	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
+	self->pos1[2] += self->enemy->viewheight;
+	self->monsterinfo.currentmove = &gladiator_move_attack_gun;
+}
+
+
+mframe_t gladiator_frames_pain [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_pain = {FRAME_pain1, FRAME_pain6, gladiator_frames_pain, gladiator_run};
+
+mframe_t gladiator_frames_pain_air [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_pain_air = {FRAME_painup1, FRAME_painup7, gladiator_frames_pain_air, gladiator_run};
+
+void gladiator_pain (edict_t *self, edict_t *, float, int)
+{
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && (self->monsterinfo.currentmove == &gladiator_move_pain))
+			self->monsterinfo.currentmove = &gladiator_move_pain_air;
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (self->velocity[2] > 100)
+		self->monsterinfo.currentmove = &gladiator_move_pain_air;
+	else
+		self->monsterinfo.currentmove = &gladiator_move_pain;
+	
+}
+
+
+void gladiator_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t gladiator_frames_death [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t gladiator_move_death = {FRAME_death1, FRAME_death22, gladiator_frames_death, gladiator_dead};
+
+void gladiator_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &gladiator_move_death;
+}
+
+
+/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_gladiator (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+
+	sound_pain1 = gi.soundindex ("gladiator/pain.wav");	
+	sound_pain2 = gi.soundindex ("gladiator/gldpain2.wav");	
+	sound_die = gi.soundindex ("gladiator/glddeth2.wav");	
+	sound_gun = gi.soundindex ("gladiator/railgun.wav");
+	sound_cleaver_swing = gi.soundindex ("gladiator/melee1.wav");
+	sound_cleaver_hit = gi.soundindex ("gladiator/melee2.wav");
+	sound_cleaver_miss = gi.soundindex ("gladiator/melee3.wav");
+	sound_idle = gi.soundindex ("gladiator/gldidle1.wav");
+	sound_search = gi.soundindex ("gladiator/gldsrch1.wav");
+	sound_sight = gi.soundindex ("gladiator/sight.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gladiatr/tris.md2");
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 64);
+
+	self->health = 400;
+	self->gib_health = -175;
+	self->mass = 400;
+
+	self->pain = gladiator_pain;
+	self->die = gladiator_die;
+
+	self->monsterinfo.stand = gladiator_stand;
+	self->monsterinfo.walk = gladiator_walk;
+	self->monsterinfo.run = gladiator_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = gladiator_attack;
+	self->monsterinfo.melee = gladiator_melee;
+	self->monsterinfo.sight = gladiator_sight;
+	self->monsterinfo.idle = gladiator_idle;
+	self->monsterinfo.search = gladiator_search;
+
+	gi.linkentity (self);
+	self->monsterinfo.currentmove = &gladiator_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
+
--- /dev/null
+++ b/xatrix/m_gladiator.h
@@ -1,0 +1,96 @@
+// G:\quake2\baseq2\models/monsters/gladiatr
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_stand6          	5
+#define FRAME_stand7          	6
+#define FRAME_walk1           	7
+#define FRAME_walk2           	8
+#define FRAME_walk3           	9
+#define FRAME_walk4           	10
+#define FRAME_walk5           	11
+#define FRAME_walk6           	12
+#define FRAME_walk7           	13
+#define FRAME_walk8           	14
+#define FRAME_walk9           	15
+#define FRAME_walk10          	16
+#define FRAME_walk11          	17
+#define FRAME_walk12          	18
+#define FRAME_walk13          	19
+#define FRAME_walk14          	20
+#define FRAME_walk15          	21
+#define FRAME_walk16          	22
+#define FRAME_run1            	23
+#define FRAME_run2            	24
+#define FRAME_run3            	25
+#define FRAME_run4            	26
+#define FRAME_run5            	27
+#define FRAME_run6            	28
+#define FRAME_melee1          	29
+#define FRAME_melee2          	30
+#define FRAME_melee3          	31
+#define FRAME_melee4          	32
+#define FRAME_melee5          	33
+#define FRAME_melee6          	34
+#define FRAME_melee7          	35
+#define FRAME_melee8          	36
+#define FRAME_melee9          	37
+#define FRAME_melee10         	38
+#define FRAME_melee11         	39
+#define FRAME_melee12         	40
+#define FRAME_melee13         	41
+#define FRAME_melee14         	42
+#define FRAME_melee15         	43
+#define FRAME_melee16         	44
+#define FRAME_melee17         	45
+#define FRAME_attack1         	46
+#define FRAME_attack2         	47
+#define FRAME_attack3         	48
+#define FRAME_attack4         	49
+#define FRAME_attack5         	50
+#define FRAME_attack6         	51
+#define FRAME_attack7         	52
+#define FRAME_attack8         	53
+#define FRAME_attack9         	54
+#define FRAME_pain1           	55
+#define FRAME_pain2           	56
+#define FRAME_pain3           	57
+#define FRAME_pain4           	58
+#define FRAME_pain5           	59
+#define FRAME_pain6           	60
+#define FRAME_death1          	61
+#define FRAME_death2          	62
+#define FRAME_death3          	63
+#define FRAME_death4          	64
+#define FRAME_death5          	65
+#define FRAME_death6          	66
+#define FRAME_death7          	67
+#define FRAME_death8          	68
+#define FRAME_death9          	69
+#define FRAME_death10         	70
+#define FRAME_death11         	71
+#define FRAME_death12         	72
+#define FRAME_death13         	73
+#define FRAME_death14         	74
+#define FRAME_death15         	75
+#define FRAME_death16         	76
+#define FRAME_death17         	77
+#define FRAME_death18         	78
+#define FRAME_death19         	79
+#define FRAME_death20         	80
+#define FRAME_death21         	81
+#define FRAME_death22         	82
+#define FRAME_painup1         	83
+#define FRAME_painup2         	84
+#define FRAME_painup3         	85
+#define FRAME_painup4         	86
+#define FRAME_painup5         	87
+#define FRAME_painup6         	88
+#define FRAME_painup7         	89
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_gunner.c
@@ -1,0 +1,605 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_gunner.h"
+
+
+static int	sound_pain;
+static int	sound_pain2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_open;
+static int	sound_search;
+static int	sound_sight;
+
+
+void gunner_idlesound (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void gunner_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void gunner_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+
+qboolean visible (edict_t *self, edict_t *other);
+void GunnerGrenade (edict_t *self);
+void GunnerFire (edict_t *self);
+void gunner_fire_chain(edict_t *self);
+void gunner_refire_chain(edict_t *self);
+
+
+void gunner_stand (edict_t *self);
+
+mframe_t gunner_frames_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_idlesound,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	gunner_move_fidget = {FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand};
+
+void gunner_fidget (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		return;
+	if (qrandom() <= 0.05)
+		self->monsterinfo.currentmove = &gunner_move_fidget;
+}
+
+mframe_t gunner_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, gunner_fidget
+};
+mmove_t	gunner_move_stand = {FRAME_stand01, FRAME_stand30, gunner_frames_stand, NULL};
+
+void gunner_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &gunner_move_stand;
+}
+
+
+mframe_t gunner_frames_walk [] =
+{
+	ai_walk, 0, NULL,
+	ai_walk, 3, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 2, NULL,
+	ai_walk, 6, NULL,
+	ai_walk, 4, NULL,
+	ai_walk, 2, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 5, NULL,
+	ai_walk, 7, NULL,
+	ai_walk, 4, NULL
+};
+mmove_t gunner_move_walk = {FRAME_walk07, FRAME_walk19, gunner_frames_walk, NULL};
+
+void gunner_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_walk;
+}
+
+mframe_t gunner_frames_run [] =
+{
+	ai_run, 26, NULL,
+	ai_run, 9,  NULL,
+	ai_run, 9,  NULL,
+	ai_run, 9,  NULL,
+	ai_run, 15, NULL,
+	ai_run, 10, NULL,
+	ai_run, 13, NULL,
+	ai_run, 6,  NULL
+};
+
+mmove_t gunner_move_run = {FRAME_run01, FRAME_run08, gunner_frames_run, NULL};
+
+void gunner_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &gunner_move_stand;
+	else
+		self->monsterinfo.currentmove = &gunner_move_run;
+}
+
+mframe_t gunner_frames_runandshoot [] =
+{
+	ai_run, 32, NULL,
+	ai_run, 15, NULL,
+	ai_run, 10, NULL,
+	ai_run, 18, NULL,
+	ai_run, 8,  NULL,
+	ai_run, 20, NULL
+};
+
+mmove_t gunner_move_runandshoot = {FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, NULL};
+
+void gunner_runandshoot (edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_runandshoot;
+}
+
+mframe_t gunner_frames_pain3 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 1,	 NULL
+};
+mmove_t gunner_move_pain3 = {FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run};
+
+mframe_t gunner_frames_pain2 [] =
+{
+	ai_move, -2, NULL,
+	ai_move, 11, NULL,
+	ai_move, 6,	 NULL,
+	ai_move, 2,	 NULL,
+	ai_move, -1, NULL,
+	ai_move, -7, NULL,
+	ai_move, -2, NULL,
+	ai_move, -7, NULL
+};
+mmove_t gunner_move_pain2 = {FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run};
+
+mframe_t gunner_frames_pain1 [] =
+{
+	ai_move, 2,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -5, NULL,
+	ai_move, 3,	 NULL,
+	ai_move, -1, NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 2,	 NULL,
+	ai_move, 1,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -2, NULL,
+	ai_move, -2, NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t gunner_move_pain1 = {FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run};
+
+void gunner_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (rand()&1)
+		gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 10)
+		self->monsterinfo.currentmove = &gunner_move_pain3;
+	else if (damage <= 25)
+		self->monsterinfo.currentmove = &gunner_move_pain2;
+	else
+		self->monsterinfo.currentmove = &gunner_move_pain1;
+}
+
+void gunner_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t gunner_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, -7, NULL,
+	ai_move, -3, NULL,
+	ai_move, -5, NULL,
+	ai_move, 8,	 NULL,
+	ai_move, 6,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t gunner_move_death = {FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead};
+
+void gunner_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &gunner_move_death;
+}
+
+
+void gunner_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	if (skill->value >= 2)
+	{
+		if (qrandom() > 0.5)
+			GunnerGrenade (self);
+	}
+
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void gunner_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void gunner_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+mframe_t gunner_frames_duck [] =
+{
+	ai_move, 1,  gunner_duck_down,
+	ai_move, 1,  NULL,
+	ai_move, 1,  gunner_duck_hold,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -1, NULL,
+	ai_move, 0,  gunner_duck_up,
+	ai_move, -1, NULL
+};
+mmove_t	gunner_move_duck = {FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run};
+
+void gunner_dodge (edict_t *self, edict_t *attacker, float)
+{
+	if (qrandom() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &gunner_move_duck;
+}
+
+
+void gunner_opengun (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
+}
+
+void GunnerFire (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	target;
+	vec3_t	aim;
+	int		flash_number;
+
+	flash_number = MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	// project enemy back a bit and target there
+	VectorCopy (self->enemy->s.origin, target);
+	VectorMA (target, -0.2, self->enemy->velocity, target);
+	target[2] += self->enemy->viewheight;
+
+	VectorSubtract (target, start, aim);
+	VectorNormalize (aim);
+	monster_fire_bullet (self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void GunnerGrenade (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	aim;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak105)
+		flash_number = MZ2_GUNNER_GRENADE_1;
+	else if (self->s.frame == FRAME_attak108)
+		flash_number = MZ2_GUNNER_GRENADE_2;
+	else if (self->s.frame == FRAME_attak111)
+		flash_number = MZ2_GUNNER_GRENADE_3;
+	else // (self->s.frame == FRAME_attak114)
+		flash_number = MZ2_GUNNER_GRENADE_4;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	//FIXME : do a spread -225 -75 75 225 degrees around forward
+	VectorCopy (forward, aim);
+
+	monster_fire_grenade (self, start, aim, 50, 600, flash_number);
+}
+
+mframe_t gunner_frames_attack_chain [] =
+{
+	/*
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	*/
+	ai_charge, 0, gunner_opengun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_attack_chain = {FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain};
+
+mframe_t gunner_frames_fire_chain [] =
+{
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire,
+	ai_charge,   0, GunnerFire
+};
+mmove_t gunner_move_fire_chain = {FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain};
+
+mframe_t gunner_frames_endfire_chain [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_endfire_chain = {FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run};
+
+mframe_t gunner_frames_attack_grenade [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, GunnerGrenade,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t gunner_move_attack_grenade = {FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run};
+
+void gunner_attack(edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+	{
+		self->monsterinfo.currentmove = &gunner_move_attack_chain;
+	}
+	else
+	{
+		if (qrandom() <= 0.5)
+			self->monsterinfo.currentmove = &gunner_move_attack_grenade;
+		else
+			self->monsterinfo.currentmove = &gunner_move_attack_chain;
+	}
+}
+
+void gunner_fire_chain(edict_t *self)
+{
+	self->monsterinfo.currentmove = &gunner_move_fire_chain;
+}
+
+void gunner_refire_chain(edict_t *self)
+{
+	if (self->enemy->health > 0)
+		if ( visible (self, self->enemy) )
+			if (qrandom() <= 0.5)
+			{
+				self->monsterinfo.currentmove = &gunner_move_fire_chain;
+				return;
+			}
+	self->monsterinfo.currentmove = &gunner_move_endfire_chain;
+}
+
+/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_gunner (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_death = gi.soundindex ("gunner/death1.wav");	
+	sound_pain = gi.soundindex ("gunner/gunpain2.wav");	
+	sound_pain2 = gi.soundindex ("gunner/gunpain1.wav");	
+	sound_idle = gi.soundindex ("gunner/gunidle1.wav");	
+	sound_open = gi.soundindex ("gunner/gunatck1.wav");	
+	sound_search = gi.soundindex ("gunner/gunsrch1.wav");	
+	sound_sight = gi.soundindex ("gunner/sight1.wav");	
+
+	gi.soundindex ("gunner/gunatck2.wav");
+	gi.soundindex ("gunner/gunatck3.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/gunner/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 175;
+	self->gib_health = -70;
+	self->mass = 200;
+
+	self->pain = gunner_pain;
+	self->die = gunner_die;
+
+	self->monsterinfo.stand = gunner_stand;
+	self->monsterinfo.walk = gunner_walk;
+	self->monsterinfo.run = gunner_run;
+	self->monsterinfo.dodge = gunner_dodge;
+	self->monsterinfo.attack = gunner_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = gunner_sight;
+	self->monsterinfo.search = gunner_search;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &gunner_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_gunner.h
@@ -1,0 +1,215 @@
+// G:\quake2\baseq2\models/gunner
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_stand41         	40
+#define FRAME_stand42         	41
+#define FRAME_stand43         	42
+#define FRAME_stand44         	43
+#define FRAME_stand45         	44
+#define FRAME_stand46         	45
+#define FRAME_stand47         	46
+#define FRAME_stand48         	47
+#define FRAME_stand49         	48
+#define FRAME_stand50         	49
+#define FRAME_stand51         	50
+#define FRAME_stand52         	51
+#define FRAME_stand53         	52
+#define FRAME_stand54         	53
+#define FRAME_stand55         	54
+#define FRAME_stand56         	55
+#define FRAME_stand57         	56
+#define FRAME_stand58         	57
+#define FRAME_stand59         	58
+#define FRAME_stand60         	59
+#define FRAME_stand61         	60
+#define FRAME_stand62         	61
+#define FRAME_stand63         	62
+#define FRAME_stand64         	63
+#define FRAME_stand65         	64
+#define FRAME_stand66         	65
+#define FRAME_stand67         	66
+#define FRAME_stand68         	67
+#define FRAME_stand69         	68
+#define FRAME_stand70         	69
+#define FRAME_walk01          	70
+#define FRAME_walk02          	71
+#define FRAME_walk03          	72
+#define FRAME_walk04          	73
+#define FRAME_walk05          	74
+#define FRAME_walk06          	75
+#define FRAME_walk07          	76
+#define FRAME_walk08          	77
+#define FRAME_walk09          	78
+#define FRAME_walk10          	79
+#define FRAME_walk11          	80
+#define FRAME_walk12          	81
+#define FRAME_walk13          	82
+#define FRAME_walk14          	83
+#define FRAME_walk15          	84
+#define FRAME_walk16          	85
+#define FRAME_walk17          	86
+#define FRAME_walk18          	87
+#define FRAME_walk19          	88
+#define FRAME_walk20          	89
+#define FRAME_walk21          	90
+#define FRAME_walk22          	91
+#define FRAME_walk23          	92
+#define FRAME_walk24          	93
+#define FRAME_run01           	94
+#define FRAME_run02           	95
+#define FRAME_run03           	96
+#define FRAME_run04           	97
+#define FRAME_run05           	98
+#define FRAME_run06           	99
+#define FRAME_run07           	100
+#define FRAME_run08           	101
+#define FRAME_runs01          	102
+#define FRAME_runs02          	103
+#define FRAME_runs03          	104
+#define FRAME_runs04          	105
+#define FRAME_runs05          	106
+#define FRAME_runs06          	107
+#define FRAME_attak101        	108
+#define FRAME_attak102        	109
+#define FRAME_attak103        	110
+#define FRAME_attak104        	111
+#define FRAME_attak105        	112
+#define FRAME_attak106        	113
+#define FRAME_attak107        	114
+#define FRAME_attak108        	115
+#define FRAME_attak109        	116
+#define FRAME_attak110        	117
+#define FRAME_attak111        	118
+#define FRAME_attak112        	119
+#define FRAME_attak113        	120
+#define FRAME_attak114        	121
+#define FRAME_attak115        	122
+#define FRAME_attak116        	123
+#define FRAME_attak117        	124
+#define FRAME_attak118        	125
+#define FRAME_attak119        	126
+#define FRAME_attak120        	127
+#define FRAME_attak121        	128
+#define FRAME_attak201        	129
+#define FRAME_attak202        	130
+#define FRAME_attak203        	131
+#define FRAME_attak204        	132
+#define FRAME_attak205        	133
+#define FRAME_attak206        	134
+#define FRAME_attak207        	135
+#define FRAME_attak208        	136
+#define FRAME_attak209        	137
+#define FRAME_attak210        	138
+#define FRAME_attak211        	139
+#define FRAME_attak212        	140
+#define FRAME_attak213        	141
+#define FRAME_attak214        	142
+#define FRAME_attak215        	143
+#define FRAME_attak216        	144
+#define FRAME_attak217        	145
+#define FRAME_attak218        	146
+#define FRAME_attak219        	147
+#define FRAME_attak220        	148
+#define FRAME_attak221        	149
+#define FRAME_attak222        	150
+#define FRAME_attak223        	151
+#define FRAME_attak224        	152
+#define FRAME_attak225        	153
+#define FRAME_attak226        	154
+#define FRAME_attak227        	155
+#define FRAME_attak228        	156
+#define FRAME_attak229        	157
+#define FRAME_attak230        	158
+#define FRAME_pain101         	159
+#define FRAME_pain102         	160
+#define FRAME_pain103         	161
+#define FRAME_pain104         	162
+#define FRAME_pain105         	163
+#define FRAME_pain106         	164
+#define FRAME_pain107         	165
+#define FRAME_pain108         	166
+#define FRAME_pain109         	167
+#define FRAME_pain110         	168
+#define FRAME_pain111         	169
+#define FRAME_pain112         	170
+#define FRAME_pain113         	171
+#define FRAME_pain114         	172
+#define FRAME_pain115         	173
+#define FRAME_pain116         	174
+#define FRAME_pain117         	175
+#define FRAME_pain118         	176
+#define FRAME_pain201         	177
+#define FRAME_pain202         	178
+#define FRAME_pain203         	179
+#define FRAME_pain204         	180
+#define FRAME_pain205         	181
+#define FRAME_pain206         	182
+#define FRAME_pain207         	183
+#define FRAME_pain208         	184
+#define FRAME_pain301         	185
+#define FRAME_pain302         	186
+#define FRAME_pain303         	187
+#define FRAME_pain304         	188
+#define FRAME_pain305         	189
+#define FRAME_death01         	190
+#define FRAME_death02         	191
+#define FRAME_death03         	192
+#define FRAME_death04         	193
+#define FRAME_death05         	194
+#define FRAME_death06         	195
+#define FRAME_death07         	196
+#define FRAME_death08         	197
+#define FRAME_death09         	198
+#define FRAME_death10         	199
+#define FRAME_death11         	200
+#define FRAME_duck01          	201
+#define FRAME_duck02          	202
+#define FRAME_duck03          	203
+#define FRAME_duck04          	204
+#define FRAME_duck05          	205
+#define FRAME_duck06          	206
+#define FRAME_duck07          	207
+#define FRAME_duck08          	208
+
+#define MODEL_SCALE		1.150000
--- /dev/null
+++ b/xatrix/m_hover.c
@@ -1,0 +1,597 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_hover.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_death1;
+static int	sound_death2;
+static int	sound_sight;
+static int	sound_search1;
+static int	sound_search2;
+
+
+void hover_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void hover_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+}
+
+
+void hover_run (edict_t *self);
+void hover_stand (edict_t *self);
+void hover_dead (edict_t *self);
+void hover_attack (edict_t *self);
+void hover_reattack (edict_t *self);
+void hover_fire_blaster (edict_t *self);
+void hover_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
+
+mframe_t hover_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	hover_move_stand = {FRAME_stand01, FRAME_stand30, hover_frames_stand, NULL};
+
+mframe_t hover_frames_stop1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_stop1 = {FRAME_stop101, FRAME_stop109, hover_frames_stop1, NULL};
+
+mframe_t hover_frames_stop2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_stop2 = {FRAME_stop201, FRAME_stop208, hover_frames_stop2, NULL};
+
+mframe_t hover_frames_takeoff [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	-1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-9,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_takeoff = {FRAME_takeof01, FRAME_takeof30, hover_frames_takeoff, NULL};
+
+mframe_t hover_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_pain3 = {FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run};
+
+mframe_t hover_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_pain2 = {FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run};
+
+mframe_t hover_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	-8,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-6,	NULL,
+	ai_move,	-4,	NULL,
+	ai_move,	-3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	7,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	4,	NULL
+};
+mmove_t hover_move_pain1 = {FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run};
+
+mframe_t hover_frames_land [] =
+{
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_land = {FRAME_land01, FRAME_land01, hover_frames_land, NULL};
+
+mframe_t hover_frames_forward [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_forward = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_forward, NULL};
+
+mframe_t hover_frames_walk [] =
+{
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL,
+	ai_walk,	4,	NULL
+};
+mmove_t hover_move_walk = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, NULL};
+
+mframe_t hover_frames_run [] =
+{
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL,
+	ai_run,	10,	NULL
+};
+mmove_t hover_move_run = {FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, NULL};
+
+mframe_t hover_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	-10,NULL,
+	ai_move,	3,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	4,	NULL,
+	ai_move,	7,	NULL
+};
+mmove_t hover_move_death1 = {FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead};
+
+mframe_t hover_frames_backward [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t hover_move_backward = {FRAME_backwd01, FRAME_backwd24, hover_frames_backward, NULL};
+
+mframe_t hover_frames_start_attack [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t hover_move_start_attack = {FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack};
+
+mframe_t hover_frames_attack1 [] =
+{
+	ai_charge,	-10,	hover_fire_blaster,
+	ai_charge,	-10,	hover_fire_blaster,
+	ai_charge,	0,		hover_reattack,
+};
+mmove_t hover_move_attack1 = {FRAME_attak104, FRAME_attak106, hover_frames_attack1, NULL};
+
+
+mframe_t hover_frames_end_attack [] =
+{
+	ai_charge,	1,	NULL,
+	ai_charge,	1,	NULL
+};
+mmove_t hover_move_end_attack = {FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run};
+
+void hover_reattack (edict_t *self)
+{
+	if (self->enemy->health > 0 )
+		if (visible (self, self->enemy) )
+			if (qrandom() <= 0.6)		
+			{
+				self->monsterinfo.currentmove = &hover_move_attack1;
+				return;
+			}
+	self->monsterinfo.currentmove = &hover_move_end_attack;
+}
+
+
+void hover_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if (self->s.frame == FRAME_attak104)
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_HOVER_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 1, 1000, MZ2_HOVER_BLASTER_1, effect);
+}
+
+
+void hover_stand (edict_t *self)
+{
+		self->monsterinfo.currentmove = &hover_move_stand;
+}
+
+void hover_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &hover_move_stand;
+	else
+		self->monsterinfo.currentmove = &hover_move_run;
+}
+
+void hover_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &hover_move_walk;
+}
+
+void hover_start_attack (edict_t *self)
+{
+	self->monsterinfo.currentmove = &hover_move_start_attack;
+}
+
+void hover_attack(edict_t *self)
+{
+	self->monsterinfo.currentmove = &hover_move_attack1;
+}
+
+
+void hover_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 25)
+	{
+		if (qrandom() < 0.5)
+		{
+			gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain3;
+		}
+		else
+		{
+			gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+			self->monsterinfo.currentmove = &hover_move_pain2;
+		}
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &hover_move_pain1;
+	}
+}
+
+void hover_deadthink (edict_t *self)
+{
+	if (!self->groundentity && level.time < self->timestamp)
+	{
+		self->nextthink = level.time + FRAMETIME;
+		return;
+	}
+	BecomeExplosion1(self);
+}
+
+void hover_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->think = hover_deadthink;
+	self->nextthink = level.time + FRAMETIME;
+	self->timestamp = level.time + 15;
+	gi.linkentity (self);
+}
+
+void hover_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &hover_move_death1;
+}
+
+/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_hover (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("hover/hovpain1.wav");	
+	sound_pain2 = gi.soundindex ("hover/hovpain2.wav");	
+	sound_death1 = gi.soundindex ("hover/hovdeth1.wav");	
+	sound_death2 = gi.soundindex ("hover/hovdeth2.wav");	
+	sound_sight = gi.soundindex ("hover/hovsght1.wav");	
+	sound_search1 = gi.soundindex ("hover/hovsrch1.wav");	
+	sound_search2 = gi.soundindex ("hover/hovsrch2.wav");	
+
+	gi.soundindex ("hover/hovatck1.wav");	
+
+	self->s.sound = gi.soundindex ("hover/hovidle1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+	self->health = 240;
+	self->gib_health = -100;
+	self->mass = 150;
+
+	self->pain = hover_pain;
+	self->die = hover_die;
+
+	self->monsterinfo.stand = hover_stand;
+	self->monsterinfo.walk = hover_walk;
+	self->monsterinfo.run = hover_run;
+//	self->monsterinfo.dodge = hover_dodge;
+	self->monsterinfo.attack = hover_start_attack;
+	self->monsterinfo.sight = hover_sight;
+	self->monsterinfo.search = hover_search;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &hover_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	flymonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_hover.h
@@ -1,0 +1,211 @@
+// G:\quake2\baseq2\models/monsters/hover
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_forwrd01        	30
+#define FRAME_forwrd02        	31
+#define FRAME_forwrd03        	32
+#define FRAME_forwrd04        	33
+#define FRAME_forwrd05        	34
+#define FRAME_forwrd06        	35
+#define FRAME_forwrd07        	36
+#define FRAME_forwrd08        	37
+#define FRAME_forwrd09        	38
+#define FRAME_forwrd10        	39
+#define FRAME_forwrd11        	40
+#define FRAME_forwrd12        	41
+#define FRAME_forwrd13        	42
+#define FRAME_forwrd14        	43
+#define FRAME_forwrd15        	44
+#define FRAME_forwrd16        	45
+#define FRAME_forwrd17        	46
+#define FRAME_forwrd18        	47
+#define FRAME_forwrd19        	48
+#define FRAME_forwrd20        	49
+#define FRAME_forwrd21        	50
+#define FRAME_forwrd22        	51
+#define FRAME_forwrd23        	52
+#define FRAME_forwrd24        	53
+#define FRAME_forwrd25        	54
+#define FRAME_forwrd26        	55
+#define FRAME_forwrd27        	56
+#define FRAME_forwrd28        	57
+#define FRAME_forwrd29        	58
+#define FRAME_forwrd30        	59
+#define FRAME_forwrd31        	60
+#define FRAME_forwrd32        	61
+#define FRAME_forwrd33        	62
+#define FRAME_forwrd34        	63
+#define FRAME_forwrd35        	64
+#define FRAME_stop101         	65
+#define FRAME_stop102         	66
+#define FRAME_stop103         	67
+#define FRAME_stop104         	68
+#define FRAME_stop105         	69
+#define FRAME_stop106         	70
+#define FRAME_stop107         	71
+#define FRAME_stop108         	72
+#define FRAME_stop109         	73
+#define FRAME_stop201         	74
+#define FRAME_stop202         	75
+#define FRAME_stop203         	76
+#define FRAME_stop204         	77
+#define FRAME_stop205         	78
+#define FRAME_stop206         	79
+#define FRAME_stop207         	80
+#define FRAME_stop208         	81
+#define FRAME_takeof01        	82
+#define FRAME_takeof02        	83
+#define FRAME_takeof03        	84
+#define FRAME_takeof04        	85
+#define FRAME_takeof05        	86
+#define FRAME_takeof06        	87
+#define FRAME_takeof07        	88
+#define FRAME_takeof08        	89
+#define FRAME_takeof09        	90
+#define FRAME_takeof10        	91
+#define FRAME_takeof11        	92
+#define FRAME_takeof12        	93
+#define FRAME_takeof13        	94
+#define FRAME_takeof14        	95
+#define FRAME_takeof15        	96
+#define FRAME_takeof16        	97
+#define FRAME_takeof17        	98
+#define FRAME_takeof18        	99
+#define FRAME_takeof19        	100
+#define FRAME_takeof20        	101
+#define FRAME_takeof21        	102
+#define FRAME_takeof22        	103
+#define FRAME_takeof23        	104
+#define FRAME_takeof24        	105
+#define FRAME_takeof25        	106
+#define FRAME_takeof26        	107
+#define FRAME_takeof27        	108
+#define FRAME_takeof28        	109
+#define FRAME_takeof29        	110
+#define FRAME_takeof30        	111
+#define FRAME_land01          	112
+#define FRAME_pain101         	113
+#define FRAME_pain102         	114
+#define FRAME_pain103         	115
+#define FRAME_pain104         	116
+#define FRAME_pain105         	117
+#define FRAME_pain106         	118
+#define FRAME_pain107         	119
+#define FRAME_pain108         	120
+#define FRAME_pain109         	121
+#define FRAME_pain110         	122
+#define FRAME_pain111         	123
+#define FRAME_pain112         	124
+#define FRAME_pain113         	125
+#define FRAME_pain114         	126
+#define FRAME_pain115         	127
+#define FRAME_pain116         	128
+#define FRAME_pain117         	129
+#define FRAME_pain118         	130
+#define FRAME_pain119         	131
+#define FRAME_pain120         	132
+#define FRAME_pain121         	133
+#define FRAME_pain122         	134
+#define FRAME_pain123         	135
+#define FRAME_pain124         	136
+#define FRAME_pain125         	137
+#define FRAME_pain126         	138
+#define FRAME_pain127         	139
+#define FRAME_pain128         	140
+#define FRAME_pain201         	141
+#define FRAME_pain202         	142
+#define FRAME_pain203         	143
+#define FRAME_pain204         	144
+#define FRAME_pain205         	145
+#define FRAME_pain206         	146
+#define FRAME_pain207         	147
+#define FRAME_pain208         	148
+#define FRAME_pain209         	149
+#define FRAME_pain210         	150
+#define FRAME_pain211         	151
+#define FRAME_pain212         	152
+#define FRAME_pain301         	153
+#define FRAME_pain302         	154
+#define FRAME_pain303         	155
+#define FRAME_pain304         	156
+#define FRAME_pain305         	157
+#define FRAME_pain306         	158
+#define FRAME_pain307         	159
+#define FRAME_pain308         	160
+#define FRAME_pain309         	161
+#define FRAME_death101        	162
+#define FRAME_death102        	163
+#define FRAME_death103        	164
+#define FRAME_death104        	165
+#define FRAME_death105        	166
+#define FRAME_death106        	167
+#define FRAME_death107        	168
+#define FRAME_death108        	169
+#define FRAME_death109        	170
+#define FRAME_death110        	171
+#define FRAME_death111        	172
+#define FRAME_backwd01        	173
+#define FRAME_backwd02        	174
+#define FRAME_backwd03        	175
+#define FRAME_backwd04        	176
+#define FRAME_backwd05        	177
+#define FRAME_backwd06        	178
+#define FRAME_backwd07        	179
+#define FRAME_backwd08        	180
+#define FRAME_backwd09        	181
+#define FRAME_backwd10        	182
+#define FRAME_backwd11        	183
+#define FRAME_backwd12        	184
+#define FRAME_backwd13        	185
+#define FRAME_backwd14        	186
+#define FRAME_backwd15        	187
+#define FRAME_backwd16        	188
+#define FRAME_backwd17        	189
+#define FRAME_backwd18        	190
+#define FRAME_backwd19        	191
+#define FRAME_backwd20        	192
+#define FRAME_backwd21        	193
+#define FRAME_backwd22        	194
+#define FRAME_backwd23        	195
+#define FRAME_backwd24        	196
+#define FRAME_attak101        	197
+#define FRAME_attak102        	198
+#define FRAME_attak103        	199
+#define FRAME_attak104        	200
+#define FRAME_attak105        	201
+#define FRAME_attak106        	202
+#define FRAME_attak107        	203
+#define FRAME_attak108        	204
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_infantry.c
@@ -1,0 +1,588 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_infantry.h"
+
+void InfantryMachineGun (edict_t *self);
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die1;
+static int	sound_die2;
+
+static int	sound_gunshot;
+static int	sound_weapon_cock;
+static int	sound_punch_swing;
+static int	sound_punch_hit;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_idle;
+
+
+mframe_t infantry_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t infantry_move_stand = {FRAME_stand50, FRAME_stand71, infantry_frames_stand, NULL};
+
+void infantry_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_stand;
+}
+
+
+mframe_t infantry_frames_fidget [] =
+{
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 3,  NULL,
+	ai_stand, 6,  NULL,
+	ai_stand, 3,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -2, NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 1,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -1, NULL,
+	ai_stand, -1, NULL,
+	ai_stand, 0,  NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -2, NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -3, NULL,
+	ai_stand, -2, NULL
+};
+mmove_t infantry_move_fidget = {FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand};
+
+void infantry_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_fidget;
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+mframe_t infantry_frames_walk [] =
+{
+	ai_walk, 5,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 5,  NULL
+};
+mmove_t infantry_move_walk = {FRAME_walk03, FRAME_walk14, infantry_frames_walk, NULL};
+
+void infantry_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &infantry_move_walk;
+}
+
+mframe_t infantry_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 20, NULL,
+	ai_run, 5,  NULL,
+	ai_run, 7,  NULL,
+	ai_run, 30, NULL,
+	ai_run, 35, NULL,
+	ai_run, 2,  NULL,
+	ai_run, 6,  NULL
+};
+mmove_t infantry_move_run = {FRAME_run01, FRAME_run08, infantry_frames_run, NULL};
+
+void infantry_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &infantry_move_stand;
+	else
+		self->monsterinfo.currentmove = &infantry_move_run;
+}
+
+
+mframe_t infantry_frames_pain1 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, -2, NULL,
+	ai_move, -1, NULL,
+	ai_move, -2, NULL,
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, -1, NULL,
+	ai_move, 1,  NULL,
+	ai_move, 6,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t infantry_move_pain1 = {FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run};
+
+mframe_t infantry_frames_pain2 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, -3, NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -2, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 5,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t infantry_move_pain2 = {FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run};
+
+void infantry_pain (edict_t *self, edict_t *, float, int)
+{
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+	
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	n = rand() % 2;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &infantry_move_pain1;
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &infantry_move_pain2;
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	}
+}
+
+
+vec3_t	aimangles[] =
+{
+	0.0, 5.0, 0.0,
+	10.0, 15.0, 0.0,
+	20.0, 25.0, 0.0,
+	25.0, 35.0, 0.0,
+	30.0, 40.0, 0.0,
+	30.0, 45.0, 0.0,
+	25.0, 50.0, 0.0,
+	20.0, 40.0, 0.0,
+	15.0, 35.0, 0.0,
+	40.0, 35.0, 0.0,
+	70.0, 35.0, 0.0,
+	90.0, 35.0, 0.0
+};
+
+void InfantryMachineGun (edict_t *self)
+{
+	vec3_t	start, target;
+	vec3_t	forward, right;
+	vec3_t	vec;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak103)
+	{
+		flash_number = MZ2_INFANTRY_MACHINEGUN_1;
+		AngleVectors (self->s.angles, forward, right, NULL);
+		G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+		if (self->enemy)
+		{
+			VectorMA (self->enemy->s.origin, -0.2, self->enemy->velocity, target);
+			target[2] += self->enemy->viewheight;
+			VectorSubtract (target, start, forward);
+			VectorNormalize (forward);
+		}
+		else
+		{
+			AngleVectors (self->s.angles, forward, right, NULL);
+		}
+	}
+	else
+	{
+		flash_number = MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211);
+
+		AngleVectors (self->s.angles, forward, right, NULL);
+		G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+		VectorSubtract (self->s.angles, aimangles[flash_number-MZ2_INFANTRY_MACHINEGUN_2], vec);
+		AngleVectors (vec, forward, NULL, NULL);
+	}
+
+	monster_fire_bullet (self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}
+
+void infantry_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_BODY, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void infantry_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	gi.linkentity (self);
+
+	M_FlyCheck (self);
+}
+
+mframe_t infantry_frames_death1 [] =
+{
+	ai_move, -4, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, -4, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, -1, NULL,
+	ai_move, 3,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, -2, NULL,
+	ai_move, 2,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 9,  NULL,
+	ai_move, 9,  NULL,
+	ai_move, 5,  NULL,
+	ai_move, -3, NULL,
+	ai_move, -3, NULL
+};
+mmove_t infantry_move_death1 = {FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead};
+
+// Off with his head
+mframe_t infantry_frames_death2 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 5,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, -3,  InfantryMachineGun,
+	ai_move, -1,  InfantryMachineGun,
+	ai_move, -2,  InfantryMachineGun,
+	ai_move, 0,   InfantryMachineGun,
+	ai_move, 2,   InfantryMachineGun,
+	ai_move, 2,   InfantryMachineGun,
+	ai_move, 3,   InfantryMachineGun,
+	ai_move, -10, InfantryMachineGun,
+	ai_move, -7,  InfantryMachineGun,
+	ai_move, -8,  InfantryMachineGun,
+	ai_move, -6,  NULL,
+	ai_move, 4,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t infantry_move_death2 = {FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead};
+
+mframe_t infantry_frames_death3 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -6,  NULL,
+	ai_move, -11, NULL,
+	ai_move, -3,  NULL,
+	ai_move, -11, NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t infantry_move_death3 = {FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead};
+
+
+void infantry_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	n = rand() % 3;
+	if (n == 0)
+	{
+		self->monsterinfo.currentmove = &infantry_move_death1;
+		gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+	}
+	else if (n == 1)
+	{
+		self->monsterinfo.currentmove = &infantry_move_death2;
+		gi.sound (self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &infantry_move_death3;
+		gi.sound (self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
+	}
+}
+
+
+void infantry_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void infantry_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void infantry_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+mframe_t infantry_frames_duck [] =
+{
+	ai_move, -2, infantry_duck_down,
+	ai_move, -5, infantry_duck_hold,
+	ai_move, 3,  NULL,
+	ai_move, 4,  infantry_duck_up,
+	ai_move, 0,  NULL
+};
+mmove_t infantry_move_duck = {FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run};
+
+void infantry_dodge (edict_t *self, edict_t *attacker, float)
+{
+	if (qrandom() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &infantry_move_duck;
+}
+
+
+void infantry_set_firetime (edict_t *self)
+{
+	int		n;
+
+	n = (rand() & 15) + 5;
+	self->monsterinfo.pausetime = level.time + n * FRAMETIME;
+}
+
+void infantry_cock_gun (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0);
+}
+
+void infantry_fire (edict_t *self)
+{
+	InfantryMachineGun (self);
+
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t infantry_frames_attack1 [] =
+{
+	ai_charge, 10, infantry_set_firetime,
+	ai_charge,  6, NULL,
+	ai_charge,  0, infantry_fire,
+	ai_charge,  0, NULL,
+	ai_charge,  1, NULL,
+	ai_charge, -7, NULL,
+	ai_charge, -6, NULL,
+	ai_charge, -1, NULL,
+	ai_charge,  0, infantry_cock_gun,
+	ai_charge,  0, NULL,
+	ai_charge,  0, NULL,
+	ai_charge,  0, NULL,
+	ai_charge,  0, NULL,
+	ai_charge, -1, NULL,
+	ai_charge, -1, NULL
+};
+mmove_t infantry_move_attack1 = {FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run};
+
+
+void infantry_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0);
+}
+
+void infantry_smack (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, 0, 0);
+	if (fire_hit (self, aim, (5 + (rand() % 5)), 50))
+		gi.sound (self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0);
+}
+
+mframe_t infantry_frames_attack2 [] =
+{
+	ai_charge, 3, NULL,
+	ai_charge, 6, NULL,
+	ai_charge, 0, infantry_swing,
+	ai_charge, 8, NULL,
+	ai_charge, 5, NULL,
+	ai_charge, 8, infantry_smack,
+	ai_charge, 6, NULL,
+	ai_charge, 3, NULL,
+};
+mmove_t infantry_move_attack2 = {FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run};
+
+void infantry_attack(edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+		self->monsterinfo.currentmove = &infantry_move_attack2;
+	else
+		self->monsterinfo.currentmove = &infantry_move_attack1;
+}
+
+
+/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_infantry (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("infantry/infpain1.wav");
+	sound_pain2 = gi.soundindex ("infantry/infpain2.wav");
+	sound_die1 = gi.soundindex ("infantry/infdeth1.wav");
+	sound_die2 = gi.soundindex ("infantry/infdeth2.wav");
+
+	sound_gunshot = gi.soundindex ("infantry/infatck1.wav");
+	sound_weapon_cock = gi.soundindex ("infantry/infatck3.wav");
+	sound_punch_swing = gi.soundindex ("infantry/infatck2.wav");
+	sound_punch_hit = gi.soundindex ("infantry/melee2.wav");
+	
+	sound_sight = gi.soundindex ("infantry/infsght1.wav");
+	sound_search = gi.soundindex ("infantry/infsrch1.wav");
+	sound_idle = gi.soundindex ("infantry/infidle1.wav");
+	
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = -40;
+	self->mass = 200;
+
+	self->pain = infantry_pain;
+	self->die = infantry_die;
+
+	self->monsterinfo.stand = infantry_stand;
+	self->monsterinfo.walk = infantry_walk;
+	self->monsterinfo.run = infantry_run;
+	self->monsterinfo.dodge = infantry_dodge;
+	self->monsterinfo.attack = infantry_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = infantry_sight;
+	self->monsterinfo.idle = infantry_fidget;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &infantry_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_infantry.h
@@ -1,0 +1,213 @@
+// G:\quake2\baseq2\models/monsters/infantry
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_gun02           	0
+#define FRAME_stand01         	1
+#define FRAME_stand02         	2
+#define FRAME_stand03         	3
+#define FRAME_stand04         	4
+#define FRAME_stand05         	5
+#define FRAME_stand06         	6
+#define FRAME_stand07         	7
+#define FRAME_stand08         	8
+#define FRAME_stand09         	9
+#define FRAME_stand10         	10
+#define FRAME_stand11         	11
+#define FRAME_stand12         	12
+#define FRAME_stand13         	13
+#define FRAME_stand14         	14
+#define FRAME_stand15         	15
+#define FRAME_stand16         	16
+#define FRAME_stand17         	17
+#define FRAME_stand18         	18
+#define FRAME_stand19         	19
+#define FRAME_stand20         	20
+#define FRAME_stand21         	21
+#define FRAME_stand22         	22
+#define FRAME_stand23         	23
+#define FRAME_stand24         	24
+#define FRAME_stand25         	25
+#define FRAME_stand26         	26
+#define FRAME_stand27         	27
+#define FRAME_stand28         	28
+#define FRAME_stand29         	29
+#define FRAME_stand30         	30
+#define FRAME_stand31         	31
+#define FRAME_stand32         	32
+#define FRAME_stand33         	33
+#define FRAME_stand34         	34
+#define FRAME_stand35         	35
+#define FRAME_stand36         	36
+#define FRAME_stand37         	37
+#define FRAME_stand38         	38
+#define FRAME_stand39         	39
+#define FRAME_stand40         	40
+#define FRAME_stand41         	41
+#define FRAME_stand42         	42
+#define FRAME_stand43         	43
+#define FRAME_stand44         	44
+#define FRAME_stand45         	45
+#define FRAME_stand46         	46
+#define FRAME_stand47         	47
+#define FRAME_stand48         	48
+#define FRAME_stand49         	49
+#define FRAME_stand50         	50
+#define FRAME_stand51         	51
+#define FRAME_stand52         	52
+#define FRAME_stand53         	53
+#define FRAME_stand54         	54
+#define FRAME_stand55         	55
+#define FRAME_stand56         	56
+#define FRAME_stand57         	57
+#define FRAME_stand58         	58
+#define FRAME_stand59         	59
+#define FRAME_stand60         	60
+#define FRAME_stand61         	61
+#define FRAME_stand62         	62
+#define FRAME_stand63         	63
+#define FRAME_stand64         	64
+#define FRAME_stand65         	65
+#define FRAME_stand66         	66
+#define FRAME_stand67         	67
+#define FRAME_stand68         	68
+#define FRAME_stand69         	69
+#define FRAME_stand70         	70
+#define FRAME_stand71         	71
+#define FRAME_walk01          	72
+#define FRAME_walk02          	73
+#define FRAME_walk03          	74
+#define FRAME_walk04          	75
+#define FRAME_walk05          	76
+#define FRAME_walk06          	77
+#define FRAME_walk07          	78
+#define FRAME_walk08          	79
+#define FRAME_walk09          	80
+#define FRAME_walk10          	81
+#define FRAME_walk11          	82
+#define FRAME_walk12          	83
+#define FRAME_walk13          	84
+#define FRAME_walk14          	85
+#define FRAME_walk15          	86
+#define FRAME_walk16          	87
+#define FRAME_walk17          	88
+#define FRAME_walk18          	89
+#define FRAME_walk19          	90
+#define FRAME_walk20          	91
+#define FRAME_run01           	92
+#define FRAME_run02           	93
+#define FRAME_run03           	94
+#define FRAME_run04           	95
+#define FRAME_run05           	96
+#define FRAME_run06           	97
+#define FRAME_run07           	98
+#define FRAME_run08           	99
+#define FRAME_pain101         	100
+#define FRAME_pain102         	101
+#define FRAME_pain103         	102
+#define FRAME_pain104         	103
+#define FRAME_pain105         	104
+#define FRAME_pain106         	105
+#define FRAME_pain107         	106
+#define FRAME_pain108         	107
+#define FRAME_pain109         	108
+#define FRAME_pain110         	109
+#define FRAME_pain201         	110
+#define FRAME_pain202         	111
+#define FRAME_pain203         	112
+#define FRAME_pain204         	113
+#define FRAME_pain205         	114
+#define FRAME_pain206         	115
+#define FRAME_pain207         	116
+#define FRAME_pain208         	117
+#define FRAME_pain209         	118
+#define FRAME_pain210         	119
+#define FRAME_duck01          	120
+#define FRAME_duck02          	121
+#define FRAME_duck03          	122
+#define FRAME_duck04          	123
+#define FRAME_duck05          	124
+#define FRAME_death101        	125
+#define FRAME_death102        	126
+#define FRAME_death103        	127
+#define FRAME_death104        	128
+#define FRAME_death105        	129
+#define FRAME_death106        	130
+#define FRAME_death107        	131
+#define FRAME_death108        	132
+#define FRAME_death109        	133
+#define FRAME_death110        	134
+#define FRAME_death111        	135
+#define FRAME_death112        	136
+#define FRAME_death113        	137
+#define FRAME_death114        	138
+#define FRAME_death115        	139
+#define FRAME_death116        	140
+#define FRAME_death117        	141
+#define FRAME_death118        	142
+#define FRAME_death119        	143
+#define FRAME_death120        	144
+#define FRAME_death201        	145
+#define FRAME_death202        	146
+#define FRAME_death203        	147
+#define FRAME_death204        	148
+#define FRAME_death205        	149
+#define FRAME_death206        	150
+#define FRAME_death207        	151
+#define FRAME_death208        	152
+#define FRAME_death209        	153
+#define FRAME_death210        	154
+#define FRAME_death211        	155
+#define FRAME_death212        	156
+#define FRAME_death213        	157
+#define FRAME_death214        	158
+#define FRAME_death215        	159
+#define FRAME_death216        	160
+#define FRAME_death217        	161
+#define FRAME_death218        	162
+#define FRAME_death219        	163
+#define FRAME_death220        	164
+#define FRAME_death221        	165
+#define FRAME_death222        	166
+#define FRAME_death223        	167
+#define FRAME_death224        	168
+#define FRAME_death225        	169
+#define FRAME_death301        	170
+#define FRAME_death302        	171
+#define FRAME_death303        	172
+#define FRAME_death304        	173
+#define FRAME_death305        	174
+#define FRAME_death306        	175
+#define FRAME_death307        	176
+#define FRAME_death308        	177
+#define FRAME_death309        	178
+#define FRAME_block01         	179
+#define FRAME_block02         	180
+#define FRAME_block03         	181
+#define FRAME_block04         	182
+#define FRAME_block05         	183
+#define FRAME_attak101        	184
+#define FRAME_attak102        	185
+#define FRAME_attak103        	186
+#define FRAME_attak104        	187
+#define FRAME_attak105        	188
+#define FRAME_attak106        	189
+#define FRAME_attak107        	190
+#define FRAME_attak108        	191
+#define FRAME_attak109        	192
+#define FRAME_attak110        	193
+#define FRAME_attak111        	194
+#define FRAME_attak112        	195
+#define FRAME_attak113        	196
+#define FRAME_attak114        	197
+#define FRAME_attak115        	198
+#define FRAME_attak201        	199
+#define FRAME_attak202        	200
+#define FRAME_attak203        	201
+#define FRAME_attak204        	202
+#define FRAME_attak205        	203
+#define FRAME_attak206        	204
+#define FRAME_attak207        	205
+#define FRAME_attak208        	206
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_insane.c
@@ -1,0 +1,670 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_insane.h"
+
+
+static int	sound_fist;
+static int	sound_shake;
+static int	sound_moan;
+static int	sound_scream[8];
+
+void insane_fist (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0);
+}
+
+void insane_shake (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0);
+}
+
+void insane_moan (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0);
+}
+
+void insane_scream (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_scream[rand()%8], 1, ATTN_IDLE, 0);
+}
+
+
+void insane_stand (edict_t *self);
+void insane_dead (edict_t *self);
+void insane_cross (edict_t *self);
+void insane_walk (edict_t *self);
+void insane_run (edict_t *self);
+void insane_checkdown (edict_t *self);
+void insane_checkup (edict_t *self);
+void insane_onground (edict_t *self);
+
+
+mframe_t insane_frames_stand_normal [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, insane_checkdown
+};
+mmove_t insane_move_stand_normal = {FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand};
+
+mframe_t insane_frames_stand_insane [] =
+{
+	ai_stand,	0,	insane_shake,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	NULL,
+	ai_stand,	0,	insane_checkdown
+};
+mmove_t insane_move_stand_insane = {FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand};
+
+mframe_t insane_frames_uptodown [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_moan,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	2.7,	NULL,
+	ai_move,	4.1,	NULL,
+	ai_move,	6,		NULL,
+	ai_move,	7.6,	NULL,
+	ai_move,	3.6,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_fist,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	insane_fist,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t insane_move_uptodown = {FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground};
+
+
+mframe_t insane_frames_downtoup [] =
+{
+	ai_move,	-0.7,	NULL,			// 41
+	ai_move,	-1.2,	NULL,			// 42
+	ai_move,	-1.5,		NULL,		// 43
+	ai_move,	-4.5,		NULL,		// 44
+	ai_move,	-3.5,	NULL,			// 45
+	ai_move,	-0.2,	NULL,			// 46
+	ai_move,	0,	NULL,			// 47
+	ai_move,	-1.3,	NULL,			// 48
+	ai_move,	-3,	NULL,				// 49
+	ai_move,	-2,	NULL,			// 50
+	ai_move,	0,	NULL,				// 51
+	ai_move,	0,	NULL,				// 52
+	ai_move,	0,	NULL,				// 53
+	ai_move,	-3.3,	NULL,			// 54
+	ai_move,	-1.6,	NULL,			// 55
+	ai_move,	-0.3,	NULL,			// 56
+	ai_move,	0,	NULL,				// 57
+	ai_move,	0,	NULL,				// 58
+	ai_move,	0,	NULL				// 59
+};
+mmove_t insane_move_downtoup = {FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand};
+
+mframe_t insane_frames_jumpdown [] =
+{
+	ai_move,	0.2,	NULL,
+	ai_move,	11.5,	NULL,
+	ai_move,	5.1,	NULL,
+	ai_move,	7.1,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t insane_move_jumpdown = {FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground};
+
+
+mframe_t insane_frames_down [] =
+{
+	ai_move,	0,		NULL,		// 100
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 110
+	ai_move,	-1.7,		NULL,
+	ai_move,	-1.6,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		insane_fist,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 120
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 130
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		insane_moan,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 140
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,		// 150
+	ai_move,	0.5,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	-0.2,		insane_scream,
+	ai_move,	0,		NULL,
+	ai_move,	0.2,		NULL,
+	ai_move,	0.4,		NULL,
+	ai_move,	0.6,		NULL,
+	ai_move,	0.8,		NULL,
+	ai_move,	0.7,		NULL,
+	ai_move,	0,		insane_checkup		// 160
+};
+mmove_t insane_move_down = {FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground};
+
+mframe_t insane_frames_walk_normal [] =
+{
+	ai_walk,	0,		insane_scream,
+	ai_walk,	2.5,	NULL,
+	ai_walk,	3.5,	NULL,
+	ai_walk,	1.7,	NULL,
+	ai_walk,	2.3,	NULL,
+	ai_walk,	2.4,	NULL,
+	ai_walk,	2.2,	NULL,
+	ai_walk,	4.2,	NULL,
+	ai_walk,	5.6,	NULL,
+	ai_walk,	3.3,	NULL,
+	ai_walk,	2.4,	NULL,
+	ai_walk,	0.9,	NULL,
+	ai_walk,	0,		NULL
+};
+mmove_t insane_move_walk_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk};
+mmove_t insane_move_run_normal = {FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run};
+
+mframe_t insane_frames_walk_insane [] =
+{
+	ai_walk,	0,		insane_scream,		// walk 1
+	ai_walk,	3.4,	NULL,		// walk 2
+	ai_walk,	3.6,	NULL,		// 3
+	ai_walk,	2.9,	NULL,		// 4
+	ai_walk,	2.2,	NULL,		// 5
+	ai_walk,	2.6,	NULL,		// 6
+	ai_walk,	0,		NULL,		// 7
+	ai_walk,	0.7,	NULL,		// 8
+	ai_walk,	4.8,	NULL,		// 9
+	ai_walk,	5.3,	NULL,		// 10
+	ai_walk,	1.1,	NULL,		// 11
+	ai_walk,	2,		NULL,		// 12
+	ai_walk,	0.5,	NULL,		// 13
+	ai_walk,	0,		NULL,		// 14
+	ai_walk,	0,		NULL,		// 15
+	ai_walk,	4.9,	NULL,		// 16
+	ai_walk,	6.7,	NULL,		// 17
+	ai_walk,	3.8,	NULL,		// 18
+	ai_walk,	2,		NULL,		// 19
+	ai_walk,	0.2,	NULL,		// 20
+	ai_walk,	0,		NULL,		// 21
+	ai_walk,	3.4,	NULL,		// 22
+	ai_walk,	6.4,	NULL,		// 23
+	ai_walk,	5,		NULL,		// 24
+	ai_walk,	1.8,	NULL,		// 25
+	ai_walk,	0,		NULL		// 26
+};
+mmove_t insane_move_walk_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk};
+mmove_t insane_move_run_insane = {FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run};
+
+mframe_t insane_frames_stand_pain [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_stand_pain = {FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run};
+
+mframe_t insane_frames_stand_death [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_stand_death = {FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead};
+
+mframe_t insane_frames_crawl [] =
+{
+	ai_walk,	0,		insane_scream,
+	ai_walk,	1.5,	NULL,
+	ai_walk,	2.1,	NULL,
+	ai_walk,	3.6,	NULL,
+	ai_walk,	2,		NULL,
+	ai_walk,	0.9,	NULL,
+	ai_walk,	3,		NULL,
+	ai_walk,	3.4,	NULL,
+	ai_walk,	2.4,	NULL
+};
+mmove_t insane_move_crawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL};
+mmove_t insane_move_runcrawl = {FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, NULL};
+
+mframe_t insane_frames_crawl_pain [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_crawl_pain = {FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run};
+
+mframe_t insane_frames_crawl_death [] =
+{
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_crawl_death = {FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead};
+
+mframe_t insane_frames_cross [] =
+{
+	ai_move,	0,		insane_moan,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_cross = {FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross};
+
+mframe_t insane_frames_struggle_cross [] =
+{
+	ai_move,	0,		insane_scream,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL,
+	ai_move,	0,		NULL
+};
+mmove_t insane_move_struggle_cross = {FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross};
+
+void insane_cross (edict_t *self)
+{
+	if (qrandom() < 0.8)		
+		self->monsterinfo.currentmove = &insane_move_cross;
+	else
+		self->monsterinfo.currentmove = &insane_move_struggle_cross;
+}
+
+void insane_walk (edict_t *self)
+{
+	if ( self->spawnflags & 16 )			// Hold Ground?
+		if (self->s.frame == FRAME_cr_pain10)
+		{
+			self->monsterinfo.currentmove = &insane_move_down;
+			return;
+		}
+	if (self->spawnflags & 4)
+		self->monsterinfo.currentmove = &insane_move_crawl;
+	else
+		if (qrandom() <= 0.5)
+			self->monsterinfo.currentmove = &insane_move_walk_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_walk_insane;
+}
+
+void insane_run (edict_t *self)
+{
+	if ( self->spawnflags & 16 )			// Hold Ground?
+		if (self->s.frame == FRAME_cr_pain10)
+		{
+			self->monsterinfo.currentmove = &insane_move_down;
+			return;
+		}
+	if (self->spawnflags & 4)				// Crawling?
+		self->monsterinfo.currentmove = &insane_move_runcrawl;
+	else
+		if (qrandom() <= 0.5)				// Else, mix it up
+			self->monsterinfo.currentmove = &insane_move_run_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_run_insane;
+}
+
+
+void insane_pain (edict_t *self, edict_t *, float, int)
+{
+	int	l,r;
+
+//	if (self->health < (self->max_health / 2))
+//		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	r = 1 + (rand()&1);
+	if (self->health < 25)
+		l = 25;
+	else if (self->health < 50)
+		l = 50;
+	else if (self->health < 75)
+		l = 75;
+	else
+		l = 100;
+	gi.sound (self, CHAN_VOICE, gi.soundindex (va("player/male/pain%i_%i.wav", l, r)), 1, ATTN_IDLE, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	// Don't go into pain frames if crucified.
+	if (self->spawnflags & 8)
+	{
+		self->monsterinfo.currentmove = &insane_move_struggle_cross;			
+		return;
+	}
+	
+	if  ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) )
+	{
+		self->monsterinfo.currentmove = &insane_move_crawl_pain;
+	}
+	else
+		self->monsterinfo.currentmove = &insane_move_stand_pain;
+
+}
+
+void insane_onground (edict_t *self)
+{
+	self->monsterinfo.currentmove = &insane_move_down;
+}
+
+void insane_checkdown (edict_t *self)
+{
+//	if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) )
+	if (self->spawnflags & 32)				// Always stand
+		return;
+	if (qrandom() < 0.3)
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &insane_move_uptodown;
+		else
+			self->monsterinfo.currentmove = &insane_move_jumpdown; 
+}
+
+void insane_checkup (edict_t *self)
+{
+	// If Hold_Ground and Crawl are set
+	if ( (self->spawnflags & 4) && (self->spawnflags & 16) )
+		return;
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &insane_move_downtoup;				
+
+}
+
+void insane_stand (edict_t *self)
+{
+	if (self->spawnflags & 8)			// If crucified
+	{
+		self->monsterinfo.currentmove = &insane_move_cross;
+		self->monsterinfo.aiflags |= AI_STAND_GROUND;
+	}
+	// If Hold_Ground and Crawl are set
+	else if ( (self->spawnflags & 4) && (self->spawnflags & 16) )
+		self->monsterinfo.currentmove = &insane_move_down;
+	else
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &insane_move_stand_normal;
+		else
+			self->monsterinfo.currentmove = &insane_move_stand_insane;
+}
+
+void insane_dead (edict_t *self)
+{
+	if (self->spawnflags & 8)
+	{
+		self->flags |= FL_FLY;
+	}
+	else
+	{
+		VectorSet (self->mins, -16, -16, -24);
+		VectorSet (self->maxs, 16, 16, -8);
+		self->movetype = MOVETYPE_TOSS;
+	}
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void insane_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_IDLE, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, gi.soundindex(va("player/male/death%i.wav", (rand()%4)+1)), 1, ATTN_IDLE, 0);
+
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	if (self->spawnflags & 8)
+	{
+		insane_dead (self);
+	}
+	else
+	{
+		if ( ((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) )		
+			self->monsterinfo.currentmove = &insane_move_crawl_death;
+		else
+			self->monsterinfo.currentmove = &insane_move_stand_death;
+	}
+}
+
+
+/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND
+*/
+void SP_misc_insane (edict_t *self)
+{
+//	static int skin = 0;	//@@
+
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_fist = gi.soundindex ("insane/insane11.wav");
+	sound_shake = gi.soundindex ("insane/insane5.wav");
+	sound_moan = gi.soundindex ("insane/insane7.wav");
+	sound_scream[0] = gi.soundindex ("insane/insane1.wav");
+	sound_scream[1] = gi.soundindex ("insane/insane2.wav");
+	sound_scream[2] = gi.soundindex ("insane/insane3.wav");
+	sound_scream[3] = gi.soundindex ("insane/insane4.wav");
+	sound_scream[4] = gi.soundindex ("insane/insane6.wav");
+	sound_scream[5] = gi.soundindex ("insane/insane8.wav");
+	sound_scream[6] = gi.soundindex ("insane/insane9.wav");
+	sound_scream[7] = gi.soundindex ("insane/insane10.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2");
+
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+
+	self->health = 100;
+	self->gib_health = -50;
+	self->mass = 300;
+
+	self->pain = insane_pain;
+	self->die = insane_die;
+
+	self->monsterinfo.stand = insane_stand;
+	self->monsterinfo.walk = insane_walk;
+	self->monsterinfo.run = insane_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = NULL;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+	self->monsterinfo.aiflags |= AI_GOOD_GUY;
+
+//@@
+//	self->s.skinnum = skin;
+//	skin++;
+//	if (skin > 12)
+//		skin = 0;
+
+	gi.linkentity (self);
+
+	if (self->spawnflags & 16)				// Stand Ground
+		self->monsterinfo.aiflags |= AI_STAND_GROUND;
+
+	self->monsterinfo.currentmove = &insane_move_stand_normal;
+	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	if (self->spawnflags & 8)					// Crucified ?
+	{
+		VectorSet (self->mins, -16, 0, 0);
+		VectorSet (self->maxs, 16, 8, 32);
+		self->flags |= FL_NO_KNOCKBACK;
+		flymonster_start (self);
+	}
+	else
+	{
+		walkmonster_start (self);
+		self->s.skinnum = rand()%3;
+	}
+}
--- /dev/null
+++ b/xatrix/m_insane.h
@@ -1,0 +1,288 @@
+// G:\quake2\baseq2\models/monsters/insane
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand1          	0
+#define FRAME_stand2          	1
+#define FRAME_stand3          	2
+#define FRAME_stand4          	3
+#define FRAME_stand5          	4
+#define FRAME_stand6          	5
+#define FRAME_stand7          	6
+#define FRAME_stand8          	7
+#define FRAME_stand9          	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_stand41         	40
+#define FRAME_stand42         	41
+#define FRAME_stand43         	42
+#define FRAME_stand44         	43
+#define FRAME_stand45         	44
+#define FRAME_stand46         	45
+#define FRAME_stand47         	46
+#define FRAME_stand48         	47
+#define FRAME_stand49         	48
+#define FRAME_stand50         	49
+#define FRAME_stand51         	50
+#define FRAME_stand52         	51
+#define FRAME_stand53         	52
+#define FRAME_stand54         	53
+#define FRAME_stand55         	54
+#define FRAME_stand56         	55
+#define FRAME_stand57         	56
+#define FRAME_stand58         	57
+#define FRAME_stand59         	58
+#define FRAME_stand60         	59
+#define FRAME_stand61         	60
+#define FRAME_stand62         	61
+#define FRAME_stand63         	62
+#define FRAME_stand64         	63
+#define FRAME_stand65         	64
+#define FRAME_stand66         	65
+#define FRAME_stand67         	66
+#define FRAME_stand68         	67
+#define FRAME_stand69         	68
+#define FRAME_stand70         	69
+#define FRAME_stand71         	70
+#define FRAME_stand72         	71
+#define FRAME_stand73         	72
+#define FRAME_stand74         	73
+#define FRAME_stand75         	74
+#define FRAME_stand76         	75
+#define FRAME_stand77         	76
+#define FRAME_stand78         	77
+#define FRAME_stand79         	78
+#define FRAME_stand80         	79
+#define FRAME_stand81         	80
+#define FRAME_stand82         	81
+#define FRAME_stand83         	82
+#define FRAME_stand84         	83
+#define FRAME_stand85         	84
+#define FRAME_stand86         	85
+#define FRAME_stand87         	86
+#define FRAME_stand88         	87
+#define FRAME_stand89         	88
+#define FRAME_stand90         	89
+#define FRAME_stand91         	90
+#define FRAME_stand92         	91
+#define FRAME_stand93         	92
+#define FRAME_stand94         	93
+#define FRAME_stand95         	94
+#define FRAME_stand96         	95
+#define FRAME_stand97         	96
+#define FRAME_stand98         	97
+#define FRAME_stand99         	98
+#define FRAME_stand100        	99
+#define FRAME_stand101        	100
+#define FRAME_stand102        	101
+#define FRAME_stand103        	102
+#define FRAME_stand104        	103
+#define FRAME_stand105        	104
+#define FRAME_stand106        	105
+#define FRAME_stand107        	106
+#define FRAME_stand108        	107
+#define FRAME_stand109        	108
+#define FRAME_stand110        	109
+#define FRAME_stand111        	110
+#define FRAME_stand112        	111
+#define FRAME_stand113        	112
+#define FRAME_stand114        	113
+#define FRAME_stand115        	114
+#define FRAME_stand116        	115
+#define FRAME_stand117        	116
+#define FRAME_stand118        	117
+#define FRAME_stand119        	118
+#define FRAME_stand120        	119
+#define FRAME_stand121        	120
+#define FRAME_stand122        	121
+#define FRAME_stand123        	122
+#define FRAME_stand124        	123
+#define FRAME_stand125        	124
+#define FRAME_stand126        	125
+#define FRAME_stand127        	126
+#define FRAME_stand128        	127
+#define FRAME_stand129        	128
+#define FRAME_stand130        	129
+#define FRAME_stand131        	130
+#define FRAME_stand132        	131
+#define FRAME_stand133        	132
+#define FRAME_stand134        	133
+#define FRAME_stand135        	134
+#define FRAME_stand136        	135
+#define FRAME_stand137        	136
+#define FRAME_stand138        	137
+#define FRAME_stand139        	138
+#define FRAME_stand140        	139
+#define FRAME_stand141        	140
+#define FRAME_stand142        	141
+#define FRAME_stand143        	142
+#define FRAME_stand144        	143
+#define FRAME_stand145        	144
+#define FRAME_stand146        	145
+#define FRAME_stand147        	146
+#define FRAME_stand148        	147
+#define FRAME_stand149        	148
+#define FRAME_stand150        	149
+#define FRAME_stand151        	150
+#define FRAME_stand152        	151
+#define FRAME_stand153        	152
+#define FRAME_stand154        	153
+#define FRAME_stand155        	154
+#define FRAME_stand156        	155
+#define FRAME_stand157        	156
+#define FRAME_stand158        	157
+#define FRAME_stand159        	158
+#define FRAME_stand160        	159
+#define FRAME_walk27          	160
+#define FRAME_walk28          	161
+#define FRAME_walk29          	162
+#define FRAME_walk30          	163
+#define FRAME_walk31          	164
+#define FRAME_walk32          	165
+#define FRAME_walk33          	166
+#define FRAME_walk34          	167
+#define FRAME_walk35          	168
+#define FRAME_walk36          	169
+#define FRAME_walk37          	170
+#define FRAME_walk38          	171
+#define FRAME_walk39          	172
+#define FRAME_walk1           	173
+#define FRAME_walk2           	174
+#define FRAME_walk3           	175
+#define FRAME_walk4           	176
+#define FRAME_walk5           	177
+#define FRAME_walk6           	178
+#define FRAME_walk7           	179
+#define FRAME_walk8           	180
+#define FRAME_walk9           	181
+#define FRAME_walk10          	182
+#define FRAME_walk11          	183
+#define FRAME_walk12          	184
+#define FRAME_walk13          	185
+#define FRAME_walk14          	186
+#define FRAME_walk15          	187
+#define FRAME_walk16          	188
+#define FRAME_walk17          	189
+#define FRAME_walk18          	190
+#define FRAME_walk19          	191
+#define FRAME_walk20          	192
+#define FRAME_walk21          	193
+#define FRAME_walk22          	194
+#define FRAME_walk23          	195
+#define FRAME_walk24          	196
+#define FRAME_walk25          	197
+#define FRAME_walk26          	198
+#define FRAME_st_pain2        	199
+#define FRAME_st_pain3        	200
+#define FRAME_st_pain4        	201
+#define FRAME_st_pain5        	202
+#define FRAME_st_pain6        	203
+#define FRAME_st_pain7        	204
+#define FRAME_st_pain8        	205
+#define FRAME_st_pain9        	206
+#define FRAME_st_pain10       	207
+#define FRAME_st_pain11       	208
+#define FRAME_st_pain12       	209
+#define FRAME_st_death2       	210
+#define FRAME_st_death3       	211
+#define FRAME_st_death4       	212
+#define FRAME_st_death5       	213
+#define FRAME_st_death6       	214
+#define FRAME_st_death7       	215
+#define FRAME_st_death8       	216
+#define FRAME_st_death9       	217
+#define FRAME_st_death10      	218
+#define FRAME_st_death11      	219
+#define FRAME_st_death12      	220
+#define FRAME_st_death13      	221
+#define FRAME_st_death14      	222
+#define FRAME_st_death15      	223
+#define FRAME_st_death16      	224
+#define FRAME_st_death17      	225
+#define FRAME_st_death18      	226
+#define FRAME_crawl1          	227
+#define FRAME_crawl2          	228
+#define FRAME_crawl3          	229
+#define FRAME_crawl4          	230
+#define FRAME_crawl5          	231
+#define FRAME_crawl6          	232
+#define FRAME_crawl7          	233
+#define FRAME_crawl8          	234
+#define FRAME_crawl9          	235
+#define FRAME_cr_pain2        	236
+#define FRAME_cr_pain3        	237
+#define FRAME_cr_pain4        	238
+#define FRAME_cr_pain5        	239
+#define FRAME_cr_pain6        	240
+#define FRAME_cr_pain7        	241
+#define FRAME_cr_pain8        	242
+#define FRAME_cr_pain9        	243
+#define FRAME_cr_pain10       	244
+#define FRAME_cr_death10      	245
+#define FRAME_cr_death11      	246
+#define FRAME_cr_death12      	247
+#define FRAME_cr_death13      	248
+#define FRAME_cr_death14      	249
+#define FRAME_cr_death15      	250
+#define FRAME_cr_death16      	251
+#define FRAME_cross1          	252
+#define FRAME_cross2          	253
+#define FRAME_cross3          	254
+#define FRAME_cross4          	255
+#define FRAME_cross5          	256
+#define FRAME_cross6          	257
+#define FRAME_cross7          	258
+#define FRAME_cross8          	259
+#define FRAME_cross9          	260
+#define FRAME_cross10         	261
+#define FRAME_cross11         	262
+#define FRAME_cross12         	263
+#define FRAME_cross13         	264
+#define FRAME_cross14         	265
+#define FRAME_cross15         	266
+#define FRAME_cross16         	267
+#define FRAME_cross17         	268
+#define FRAME_cross18         	269
+#define FRAME_cross19         	270
+#define FRAME_cross20         	271
+#define FRAME_cross21         	272
+#define FRAME_cross22         	273
+#define FRAME_cross23         	274
+#define FRAME_cross24         	275
+#define FRAME_cross25         	276
+#define FRAME_cross26         	277
+#define FRAME_cross27         	278
+#define FRAME_cross28         	279
+#define FRAME_cross29         	280
+#define FRAME_cross30         	281
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_medic.c
@@ -1,0 +1,746 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_medic.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+
+static int	sound_idle1;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_hook_launch;
+static int	sound_hook_hit;
+static int	sound_hook_heal;
+static int	sound_hook_retract;
+
+
+edict_t *medic_FindDeadMonster (edict_t *self)
+{
+	edict_t	*ent = NULL;
+	edict_t	*best = NULL;
+
+	while ((ent = findradius(ent, self->s.origin, 1024)) != NULL)
+	{
+		if (ent == self)
+			continue;
+		if (!(ent->svflags & SVF_MONSTER))
+			continue;
+		if (ent->monsterinfo.aiflags & AI_GOOD_GUY)
+			continue;
+		if (ent->owner)
+			continue;
+		if (ent->health > 0)
+			continue;
+		if (ent->nextthink)
+			continue;
+		if (!visible(self, ent))
+			continue;
+		if (!best)
+		{
+			best = ent;
+			continue;
+		}
+		if (ent->max_health <= best->max_health)
+			continue;
+		best = ent;
+	}
+
+	return best;
+}
+
+void medic_idle (edict_t *self)
+{
+	edict_t	*ent;
+
+	gi.sound (self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
+
+	ent = medic_FindDeadMonster(self);
+	if (ent)
+	{
+		self->enemy = ent;
+		self->enemy->owner = self;
+		self->monsterinfo.aiflags |= AI_MEDIC;
+		FoundTarget (self);
+	}
+}
+
+void medic_search (edict_t *self)
+{
+	edict_t	*ent;
+
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_IDLE, 0);
+
+	if (!self->oldenemy)
+	{
+		ent = medic_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->owner = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+		}
+	}
+}
+
+void medic_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+
+mframe_t medic_frames_stand [] =
+{
+	ai_stand, 0, medic_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+};
+mmove_t medic_move_stand = {FRAME_wait1, FRAME_wait90, medic_frames_stand, NULL};
+
+void medic_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &medic_move_stand;
+}
+
+
+mframe_t medic_frames_walk [] =
+{
+	ai_walk, 6.2,	NULL,
+	ai_walk, 18.1,  NULL,
+	ai_walk, 1,		NULL,
+	ai_walk, 9,		NULL,
+	ai_walk, 10,	NULL,
+	ai_walk, 9,		NULL,
+	ai_walk, 11,	NULL,
+	ai_walk, 11.6,  NULL,
+	ai_walk, 2,		NULL,
+	ai_walk, 9.9,	NULL,
+	ai_walk, 14,	NULL,
+	ai_walk, 9.3,	NULL
+};
+mmove_t medic_move_walk = {FRAME_walk1, FRAME_walk12, medic_frames_walk, NULL};
+
+void medic_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &medic_move_walk;
+}
+
+
+mframe_t medic_frames_run [] =
+{
+	ai_run, 18,		NULL,
+	ai_run, 22.5,	NULL,
+	ai_run, 25.4,	NULL,
+	ai_run, 23.4,	NULL,
+	ai_run, 24,		NULL,
+	ai_run, 35.6,	NULL
+	
+};
+mmove_t medic_move_run = {FRAME_run1, FRAME_run6, medic_frames_run, NULL};
+
+void medic_run (edict_t *self)
+{
+	if (!(self->monsterinfo.aiflags & AI_MEDIC))
+	{
+		edict_t	*ent;
+
+		ent = medic_FindDeadMonster(self);
+		if (ent)
+		{
+			self->oldenemy = self->enemy;
+			self->enemy = ent;
+			self->enemy->owner = self;
+			self->monsterinfo.aiflags |= AI_MEDIC;
+			FoundTarget (self);
+			return;
+		}
+	}
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &medic_move_stand;
+	else
+		self->monsterinfo.currentmove = &medic_move_run;
+}
+
+
+mframe_t medic_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_pain1 = {FRAME_paina1, FRAME_paina8, medic_frames_pain1, medic_run};
+
+mframe_t medic_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_pain2 = {FRAME_painb1, FRAME_painb15, medic_frames_pain2, medic_run};
+
+void medic_pain (edict_t *self, edict_t *, float, int)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (qrandom() < 0.5)
+	{
+		self->monsterinfo.currentmove = &medic_move_pain1;
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &medic_move_pain2;
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+	}
+}
+
+void medic_fire_blaster (edict_t *self)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	end;
+	vec3_t	dir;
+	int		effect;
+
+	if ((self->s.frame == FRAME_attack9) || (self->s.frame == FRAME_attack12))
+		effect = EF_BLASTER;
+	else if ((self->s.frame == FRAME_attack19) || (self->s.frame == FRAME_attack22) || (self->s.frame == FRAME_attack25) || (self->s.frame == FRAME_attack28))
+		effect = EF_HYPERBLASTER;
+	else
+		effect = 0;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[MZ2_MEDIC_BLASTER_1], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 2, 1000, MZ2_MEDIC_BLASTER_1, effect);
+}
+
+
+void medic_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t medic_frames_death [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t medic_move_death = {FRAME_death1, FRAME_death30, medic_frames_death, medic_dead};
+
+void medic_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	// if we had a pending patient, free him up for another medic
+	if ((self->enemy) && (self->enemy->owner == self))
+		self->enemy->owner = NULL;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &medic_move_death;
+}
+
+
+void medic_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void medic_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+void medic_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+mframe_t medic_frames_duck [] =
+{
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	medic_duck_down,
+	ai_move, -1,	medic_duck_hold,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	medic_duck_up,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL,
+	ai_move, -1,	NULL
+};
+mmove_t medic_move_duck = {FRAME_duck1, FRAME_duck16, medic_frames_duck, medic_run};
+
+void medic_dodge (edict_t *self, edict_t *attacker, float)
+{
+	if (qrandom() > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	self->monsterinfo.currentmove = &medic_move_duck;
+}
+
+mframe_t medic_frames_attackHyperBlaster [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	medic_fire_blaster
+};
+mmove_t medic_move_attackHyperBlaster = {FRAME_attack15, FRAME_attack30, medic_frames_attackHyperBlaster, medic_run};
+
+
+void medic_continue (edict_t *self)
+{
+	if (visible (self, self->enemy) )
+		if (qrandom() <= 0.95)
+			self->monsterinfo.currentmove = &medic_move_attackHyperBlaster;
+}
+
+
+mframe_t medic_frames_attackBlaster [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 2,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_fire_blaster,	
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	medic_continue	// Change to medic_continue... Else, go to frame 32
+};
+mmove_t medic_move_attackBlaster = {FRAME_attack1, FRAME_attack14, medic_frames_attackBlaster, medic_run};
+
+
+void medic_hook_launch (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_hook_launch, 1, ATTN_NORM, 0);
+}
+
+void ED_CallSpawn (edict_t *ent);
+
+static vec3_t	medic_cable_offsets[] =
+{
+	45.0,  -9.2, 15.5,
+	48.4,  -9.7, 15.2,
+	47.8,  -9.8, 15.8,
+	47.3,  -9.3, 14.3,
+	45.4, -10.1, 13.1,
+	41.9, -12.7, 12.0,
+	37.8, -15.8, 11.2,
+	34.3, -18.4, 10.7,
+	32.7, -19.7, 10.4,
+	32.7, -19.7, 10.4
+};
+
+void medic_cable_attack (edict_t *self)
+{
+	vec3_t	offset, start, end, f, r;
+	trace_t	tr;
+	vec3_t	dir, angles;
+	float	distance;
+
+	if (!self->enemy->inuse)
+		return;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorCopy (medic_cable_offsets[self->s.frame - FRAME_attack42], offset);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	// check for max distance
+	VectorSubtract (start, self->enemy->s.origin, dir);
+	distance = VectorLength(dir);
+	if (distance > 256)
+		return;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 45)
+		return;
+
+	tr = gi.trace (start, NULL, NULL, self->enemy->s.origin, self, MASK_SHOT);
+	if (tr.fraction != 1.0 && tr.ent != self->enemy)
+		return;
+
+	if (self->s.frame == FRAME_attack43)
+	{
+		gi.sound (self->enemy, CHAN_AUTO, sound_hook_hit, 1, ATTN_NORM, 0);
+		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+	}
+	else if (self->s.frame == FRAME_attack50)
+	{
+		self->enemy->spawnflags = 0;
+		self->enemy->monsterinfo.aiflags = 0;
+		self->enemy->target = NULL;
+		self->enemy->targetname = NULL;
+		self->enemy->combattarget = NULL;
+		self->enemy->deathtarget = NULL;
+		self->enemy->owner = self;
+		ED_CallSpawn (self->enemy);
+		self->enemy->owner = NULL;
+		if (self->enemy->think)
+		{
+			self->enemy->nextthink = level.time;
+			self->enemy->think (self->enemy);
+		}
+		self->enemy->monsterinfo.aiflags |= AI_RESURRECTING;
+		if (self->oldenemy && self->oldenemy->client)
+		{
+			self->enemy->enemy = self->oldenemy;
+			FoundTarget (self->enemy);
+		}
+	}
+	else
+	{
+		if (self->s.frame == FRAME_attack44)
+			gi.sound (self, CHAN_WEAPON, sound_hook_heal, 1, ATTN_NORM, 0);
+	}
+
+	// adjust start for beam origin being in middle of a segment
+	VectorMA (start, 8, f, start);
+
+	// adjust end z for end spot since the monster is currently dead
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] = self->enemy->absmin[2] + self->enemy->size[2] / 2;
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+}
+
+void medic_hook_retract (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_hook_retract, 1, ATTN_NORM, 0);
+	self->enemy->monsterinfo.aiflags &= ~AI_RESURRECTING;
+}
+
+mframe_t medic_frames_attackCable [] =
+{
+	ai_move, 2,		NULL,
+	ai_move, 3,		NULL,
+	ai_move, 5,		NULL,
+	ai_move, 4.4,	NULL,
+	ai_charge, 4.7,	NULL,
+	ai_charge, 5,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 4,	NULL,
+	ai_charge, 0,	NULL,
+	ai_move, 0,		medic_hook_launch,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, 0,		medic_cable_attack,
+	ai_move, -15,	medic_hook_retract,
+	ai_move, -1.5,	NULL,
+	ai_move, -1.2,	NULL,
+	ai_move, -3,	NULL,
+	ai_move, -2,	NULL,
+	ai_move, 0.3,	NULL,
+	ai_move, 0.7,	NULL,
+	ai_move, 1.2,	NULL,
+	ai_move, 1.3,	NULL
+};
+mmove_t medic_move_attackCable = {FRAME_attack33, FRAME_attack60, medic_frames_attackCable, medic_run};
+
+
+void medic_attack(edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+		self->monsterinfo.currentmove = &medic_move_attackCable;
+	else
+		self->monsterinfo.currentmove = &medic_move_attackBlaster;
+}
+
+qboolean medic_checkattack (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_MEDIC)
+	{
+		medic_attack(self);
+		return true;
+	}
+
+	return M_CheckAttack (self);
+}
+
+
+/*QUAKED monster_medic (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_medic (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_idle1 = gi.soundindex ("medic/idle.wav");
+	sound_pain1 = gi.soundindex ("medic/medpain1.wav");
+	sound_pain2 = gi.soundindex ("medic/medpain2.wav");
+	sound_die = gi.soundindex ("medic/meddeth1.wav");
+	sound_sight = gi.soundindex ("medic/medsght1.wav");
+	sound_search = gi.soundindex ("medic/medsrch1.wav");
+	sound_hook_launch = gi.soundindex ("medic/medatck2.wav");
+	sound_hook_hit = gi.soundindex ("medic/medatck3.wav");
+	sound_hook_heal = gi.soundindex ("medic/medatck4.wav");
+	sound_hook_retract = gi.soundindex ("medic/medatck5.wav");
+
+	gi.soundindex ("medic/medatck1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/medic/tris.md2");
+	VectorSet (self->mins, -24, -24, -24);
+	VectorSet (self->maxs, 24, 24, 32);
+
+	self->health = 300;
+	self->gib_health = -130;
+	self->mass = 400;
+
+	self->pain = medic_pain;
+	self->die = medic_die;
+
+	self->monsterinfo.stand = medic_stand;
+	self->monsterinfo.walk = medic_walk;
+	self->monsterinfo.run = medic_run;
+	self->monsterinfo.dodge = medic_dodge;
+	self->monsterinfo.attack = medic_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = medic_sight;
+	self->monsterinfo.idle = medic_idle;
+	self->monsterinfo.search = medic_search;
+	self->monsterinfo.checkattack = medic_checkattack;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &medic_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_medic.h
@@ -1,0 +1,243 @@
+// G:\quake2\baseq2\models/monsters/medic
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_walk1           	0
+#define FRAME_walk2           	1
+#define FRAME_walk3           	2
+#define FRAME_walk4           	3
+#define FRAME_walk5           	4
+#define FRAME_walk6           	5
+#define FRAME_walk7           	6
+#define FRAME_walk8           	7
+#define FRAME_walk9           	8
+#define FRAME_walk10          	9
+#define FRAME_walk11          	10
+#define FRAME_walk12          	11
+#define FRAME_wait1           	12
+#define FRAME_wait2           	13
+#define FRAME_wait3           	14
+#define FRAME_wait4           	15
+#define FRAME_wait5           	16
+#define FRAME_wait6           	17
+#define FRAME_wait7           	18
+#define FRAME_wait8           	19
+#define FRAME_wait9           	20
+#define FRAME_wait10          	21
+#define FRAME_wait11          	22
+#define FRAME_wait12          	23
+#define FRAME_wait13          	24
+#define FRAME_wait14          	25
+#define FRAME_wait15          	26
+#define FRAME_wait16          	27
+#define FRAME_wait17          	28
+#define FRAME_wait18          	29
+#define FRAME_wait19          	30
+#define FRAME_wait20          	31
+#define FRAME_wait21          	32
+#define FRAME_wait22          	33
+#define FRAME_wait23          	34
+#define FRAME_wait24          	35
+#define FRAME_wait25          	36
+#define FRAME_wait26          	37
+#define FRAME_wait27          	38
+#define FRAME_wait28          	39
+#define FRAME_wait29          	40
+#define FRAME_wait30          	41
+#define FRAME_wait31          	42
+#define FRAME_wait32          	43
+#define FRAME_wait33          	44
+#define FRAME_wait34          	45
+#define FRAME_wait35          	46
+#define FRAME_wait36          	47
+#define FRAME_wait37          	48
+#define FRAME_wait38          	49
+#define FRAME_wait39          	50
+#define FRAME_wait40          	51
+#define FRAME_wait41          	52
+#define FRAME_wait42          	53
+#define FRAME_wait43          	54
+#define FRAME_wait44          	55
+#define FRAME_wait45          	56
+#define FRAME_wait46          	57
+#define FRAME_wait47          	58
+#define FRAME_wait48          	59
+#define FRAME_wait49          	60
+#define FRAME_wait50          	61
+#define FRAME_wait51          	62
+#define FRAME_wait52          	63
+#define FRAME_wait53          	64
+#define FRAME_wait54          	65
+#define FRAME_wait55          	66
+#define FRAME_wait56          	67
+#define FRAME_wait57          	68
+#define FRAME_wait58          	69
+#define FRAME_wait59          	70
+#define FRAME_wait60          	71
+#define FRAME_wait61          	72
+#define FRAME_wait62          	73
+#define FRAME_wait63          	74
+#define FRAME_wait64          	75
+#define FRAME_wait65          	76
+#define FRAME_wait66          	77
+#define FRAME_wait67          	78
+#define FRAME_wait68          	79
+#define FRAME_wait69          	80
+#define FRAME_wait70          	81
+#define FRAME_wait71          	82
+#define FRAME_wait72          	83
+#define FRAME_wait73          	84
+#define FRAME_wait74          	85
+#define FRAME_wait75          	86
+#define FRAME_wait76          	87
+#define FRAME_wait77          	88
+#define FRAME_wait78          	89
+#define FRAME_wait79          	90
+#define FRAME_wait80          	91
+#define FRAME_wait81          	92
+#define FRAME_wait82          	93
+#define FRAME_wait83          	94
+#define FRAME_wait84          	95
+#define FRAME_wait85          	96
+#define FRAME_wait86          	97
+#define FRAME_wait87          	98
+#define FRAME_wait88          	99
+#define FRAME_wait89          	100
+#define FRAME_wait90          	101
+#define FRAME_run1            	102
+#define FRAME_run2            	103
+#define FRAME_run3            	104
+#define FRAME_run4            	105
+#define FRAME_run5            	106
+#define FRAME_run6            	107
+#define FRAME_paina1          	108
+#define FRAME_paina2          	109
+#define FRAME_paina3          	110
+#define FRAME_paina4          	111
+#define FRAME_paina5          	112
+#define FRAME_paina6          	113
+#define FRAME_paina7          	114
+#define FRAME_paina8          	115
+#define FRAME_painb1          	116
+#define FRAME_painb2          	117
+#define FRAME_painb3          	118
+#define FRAME_painb4          	119
+#define FRAME_painb5          	120
+#define FRAME_painb6          	121
+#define FRAME_painb7          	122
+#define FRAME_painb8          	123
+#define FRAME_painb9          	124
+#define FRAME_painb10         	125
+#define FRAME_painb11         	126
+#define FRAME_painb12         	127
+#define FRAME_painb13         	128
+#define FRAME_painb14         	129
+#define FRAME_painb15         	130
+#define FRAME_duck1           	131
+#define FRAME_duck2           	132
+#define FRAME_duck3           	133
+#define FRAME_duck4           	134
+#define FRAME_duck5           	135
+#define FRAME_duck6           	136
+#define FRAME_duck7           	137
+#define FRAME_duck8           	138
+#define FRAME_duck9           	139
+#define FRAME_duck10          	140
+#define FRAME_duck11          	141
+#define FRAME_duck12          	142
+#define FRAME_duck13          	143
+#define FRAME_duck14          	144
+#define FRAME_duck15          	145
+#define FRAME_duck16          	146
+#define FRAME_death1          	147
+#define FRAME_death2          	148
+#define FRAME_death3          	149
+#define FRAME_death4          	150
+#define FRAME_death5          	151
+#define FRAME_death6          	152
+#define FRAME_death7          	153
+#define FRAME_death8          	154
+#define FRAME_death9          	155
+#define FRAME_death10         	156
+#define FRAME_death11         	157
+#define FRAME_death12         	158
+#define FRAME_death13         	159
+#define FRAME_death14         	160
+#define FRAME_death15         	161
+#define FRAME_death16         	162
+#define FRAME_death17         	163
+#define FRAME_death18         	164
+#define FRAME_death19         	165
+#define FRAME_death20         	166
+#define FRAME_death21         	167
+#define FRAME_death22         	168
+#define FRAME_death23         	169
+#define FRAME_death24         	170
+#define FRAME_death25         	171
+#define FRAME_death26         	172
+#define FRAME_death27         	173
+#define FRAME_death28         	174
+#define FRAME_death29         	175
+#define FRAME_death30         	176
+#define FRAME_attack1         	177
+#define FRAME_attack2         	178
+#define FRAME_attack3         	179
+#define FRAME_attack4         	180
+#define FRAME_attack5         	181
+#define FRAME_attack6         	182
+#define FRAME_attack7         	183
+#define FRAME_attack8         	184
+#define FRAME_attack9         	185
+#define FRAME_attack10        	186
+#define FRAME_attack11        	187
+#define FRAME_attack12        	188
+#define FRAME_attack13        	189
+#define FRAME_attack14        	190
+#define FRAME_attack15        	191
+#define FRAME_attack16        	192
+#define FRAME_attack17        	193
+#define FRAME_attack18        	194
+#define FRAME_attack19        	195
+#define FRAME_attack20        	196
+#define FRAME_attack21        	197
+#define FRAME_attack22        	198
+#define FRAME_attack23        	199
+#define FRAME_attack24        	200
+#define FRAME_attack25        	201
+#define FRAME_attack26        	202
+#define FRAME_attack27        	203
+#define FRAME_attack28        	204
+#define FRAME_attack29        	205
+#define FRAME_attack30        	206
+#define FRAME_attack31        	207
+#define FRAME_attack32        	208
+#define FRAME_attack33        	209
+#define FRAME_attack34        	210
+#define FRAME_attack35        	211
+#define FRAME_attack36        	212
+#define FRAME_attack37        	213
+#define FRAME_attack38        	214
+#define FRAME_attack39        	215
+#define FRAME_attack40        	216
+#define FRAME_attack41        	217
+#define FRAME_attack42        	218
+#define FRAME_attack43        	219
+#define FRAME_attack44        	220
+#define FRAME_attack45        	221
+#define FRAME_attack46        	222
+#define FRAME_attack47        	223
+#define FRAME_attack48        	224
+#define FRAME_attack49        	225
+#define FRAME_attack50        	226
+#define FRAME_attack51        	227
+#define FRAME_attack52        	228
+#define FRAME_attack53        	229
+#define FRAME_attack54        	230
+#define FRAME_attack55        	231
+#define FRAME_attack56        	232
+#define FRAME_attack57        	233
+#define FRAME_attack58        	234
+#define FRAME_attack59        	235
+#define FRAME_attack60        	236
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_move.c
@@ -1,0 +1,568 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define	STEPSIZE	18
+
+/*
+=============
+M_CheckBottom
+
+Returns false if any part of the bottom of the entity is off an edge that
+is not a staircase.
+
+=============
+*/
+int c_yes, c_no;
+
+qboolean M_CheckBottom (edict_t *ent)
+{
+	vec3_t	mins, maxs, start, stop;
+	trace_t	trace;
+	int		x, y;
+	float	mid, bottom;
+	
+	VectorAdd (ent->s.origin, ent->mins, mins);
+	VectorAdd (ent->s.origin, ent->maxs, maxs);
+
+// if all of the points under the corners are solid world, don't bother
+// with the tougher checks
+// the corners must be within 16 of the midpoint
+	start[2] = mins[2] - 1;
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = x ? maxs[0] : mins[0];
+			start[1] = y ? maxs[1] : mins[1];
+			if (gi.pointcontents (start) != CONTENTS_SOLID)
+				goto realcheck;
+		}
+
+	c_yes++;
+	return true;		// we got out easy
+
+realcheck:
+	c_no++;
+//
+// check it for real...
+//
+	start[2] = mins[2];
+	
+// the midpoint must be within 16 of the bottom
+	start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
+	start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
+	stop[2] = start[2] - 2*STEPSIZE;
+	trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+
+	if (trace.fraction == 1.0)
+		return false;
+	mid = bottom = trace.endpos[2];
+	
+// the corners must be within 16 of the midpoint	
+	for	(x=0 ; x<=1 ; x++)
+		for	(y=0 ; y<=1 ; y++)
+		{
+			start[0] = stop[0] = x ? maxs[0] : mins[0];
+			start[1] = stop[1] = y ? maxs[1] : mins[1];
+			
+			trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID);
+			
+			if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
+				bottom = trace.endpos[2];
+			if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE)
+				return false;
+		}
+
+	c_yes++;
+	return true;
+}
+
+
+/*
+=============
+SV_movestep
+
+Called by monster program code.
+The move will be adjusted for slopes and stairs, but if the move isn't
+possible, no move is done, false is returned, and
+pr_global_struct->trace_normal is set to the normal of the blocking wall
+=============
+*/
+//FIXME since we need to test end position contents here, can we avoid doing
+//it again later in catagorize position?
+qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink)
+{
+	float		dz;
+	vec3_t		oldorg, neworg, end;
+	trace_t		trace;
+	int			i;
+	float		stepsize;
+	vec3_t		test;
+	int			contents;
+
+// try the move	
+	VectorCopy (ent->s.origin, oldorg);
+	VectorAdd (ent->s.origin, move, neworg);
+
+// flying monsters don't step up
+	if ( ent->flags & (FL_SWIM | FL_FLY) )
+	{
+	// try one move with vertical motion, then one without
+		for (i=0 ; i<2 ; i++)
+		{
+			VectorAdd (ent->s.origin, move, neworg);
+			if (i == 0 && ent->enemy)
+			{
+				if (!ent->goalentity)
+					ent->goalentity = ent->enemy;
+				dz = ent->s.origin[2] - ent->goalentity->s.origin[2];
+				if (ent->goalentity->client)
+				{
+					if (dz > 40)
+						neworg[2] -= 8;
+					if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2)))
+						if (dz < 30)
+							neworg[2] += 8;
+				}
+				else
+				{
+					// RAFAEL
+					if (strcmp (ent->classname , "monster_fixbot") == 0)
+					{
+						if (ent->s.frame >= 105 && ent->s.frame <= 120)
+						{
+							if (dz > 12)
+								neworg[2] --;
+							else if (dz < -12)
+								neworg[2] ++;
+						}
+						else if (ent->s.frame >= 31 && ent->s.frame <= 88)
+						{
+							if (dz > 12)
+								neworg[2] -= 12;
+							else if (dz < -12)
+								neworg[2] += 12;
+						}
+						else
+						{
+							if (dz > 12)
+								neworg[2] -= 8;
+							else if (dz < -12)
+								neworg[2] += 8;
+						}
+					}
+					// RAFAEL ( else )
+					else
+					{
+						if (dz > 8)
+							neworg[2] -= 8;
+						else if (dz > 0)
+							neworg[2] -= dz;
+						else if (dz < -8)
+							neworg[2] += 8;
+						else
+							neworg[2] += dz;
+					}
+				}
+			}
+			trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID);
+	
+			// fly monsters don't enter water voluntarily
+			if (ent->flags & FL_FLY)
+			{
+				if (!ent->waterlevel)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (contents & MASK_WATER)
+						return false;
+				}
+			}
+
+			// swim monsters don't exit water voluntarily
+			if (ent->flags & FL_SWIM)
+			{
+				if (ent->waterlevel < 2)
+				{
+					test[0] = trace.endpos[0];
+					test[1] = trace.endpos[1];
+					test[2] = trace.endpos[2] + ent->mins[2] + 1;
+					contents = gi.pointcontents(test);
+					if (!(contents & MASK_WATER))
+						return false;
+				}
+			}
+
+			if (trace.fraction == 1)
+			{
+				VectorCopy (trace.endpos, ent->s.origin);
+				if (relink)
+				{
+					gi.linkentity (ent);
+					G_TouchTriggers (ent);
+				}
+				return true;
+			}
+			
+			if (!ent->enemy)
+				break;
+		}
+		
+		return false;
+	}
+
+// push down from a step height above the wished position
+	if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
+		stepsize = STEPSIZE;
+	else
+		stepsize = 1;
+
+	neworg[2] += stepsize;
+	VectorCopy (neworg, end);
+	end[2] -= stepsize*2;
+
+	trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+
+	if (trace.allsolid)
+		return false;
+
+	if (trace.startsolid)
+	{
+		neworg[2] -= stepsize;
+		trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID);
+		if (trace.allsolid || trace.startsolid)
+			return false;
+	}
+
+
+	// don't go in to water
+	if (ent->waterlevel == 0)
+	{
+		test[0] = trace.endpos[0];
+		test[1] = trace.endpos[1];
+		test[2] = trace.endpos[2] + ent->mins[2] + 1;	
+		contents = gi.pointcontents(test);
+
+		if (contents & MASK_WATER)
+			return false;
+	}
+
+	if (trace.fraction == 1)
+	{
+	// if monster had the ground pulled out, go ahead and fall
+		if ( ent->flags & FL_PARTIALGROUND )
+		{
+			VectorAdd (ent->s.origin, move, ent->s.origin);
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			ent->groundentity = NULL;
+			return true;
+		}
+	
+		return false;		// walked off an edge
+	}
+
+// check point traces down for dangling corners
+	VectorCopy (trace.endpos, ent->s.origin);
+	
+	if (!M_CheckBottom (ent))
+	{
+		if ( ent->flags & FL_PARTIALGROUND )
+		{	// entity had floor mostly pulled out from underneath it
+			// and is trying to correct
+			if (relink)
+			{
+				gi.linkentity (ent);
+				G_TouchTriggers (ent);
+			}
+			return true;
+		}
+		VectorCopy (oldorg, ent->s.origin);
+		return false;
+	}
+
+	if ( ent->flags & FL_PARTIALGROUND )
+	{
+		ent->flags &= ~FL_PARTIALGROUND;
+	}
+	ent->groundentity = trace.ent;
+	ent->groundentity_linkcount = trace.ent->linkcount;
+
+// the move is ok
+	if (relink)
+	{
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+	}
+	return true;
+}
+
+
+//============================================================================
+
+/*
+===============
+M_ChangeYaw
+
+===============
+*/
+void M_ChangeYaw (edict_t *ent)
+{
+	float	ideal;
+	float	current;
+	float	move;
+	float	speed;
+	
+	current = anglemod(ent->s.angles[YAW]);
+	ideal = ent->ideal_yaw;
+
+	if (current == ideal)
+		return;
+
+	move = ideal - current;
+	speed = ent->yaw_speed;
+	if (ideal > current)
+	{
+		if (move >= 180)
+			move = move - 360;
+	}
+	else
+	{
+		if (move <= -180)
+			move = move + 360;
+	}
+	if (move > 0)
+	{
+		if (move > speed)
+			move = speed;
+	}
+	else
+	{
+		if (move < -speed)
+			move = -speed;
+	}
+	
+	ent->s.angles[YAW] = anglemod (current + move);
+}
+
+
+/*
+======================
+SV_StepDirection
+
+Turns to the movement direction, and walks the current distance if
+facing it.
+
+======================
+*/
+qboolean SV_StepDirection (edict_t *ent, float yaw, float dist)
+{
+	vec3_t		move, oldorigin;
+	float		delta;
+	
+	ent->ideal_yaw = yaw;
+	M_ChangeYaw (ent);
+	
+	yaw = yaw*M_PI*2 / 360;
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	VectorCopy (ent->s.origin, oldorigin);
+	if (SV_movestep (ent, move, false))
+	{
+		delta = ent->s.angles[YAW] - ent->ideal_yaw;
+		if (delta > 45 && delta < 315)
+		{		// not turned far enough, so don't take the step
+			VectorCopy (oldorigin, ent->s.origin);
+		}
+		gi.linkentity (ent);
+		G_TouchTriggers (ent);
+		return true;
+	}
+	gi.linkentity (ent);
+	G_TouchTriggers (ent);
+	return false;
+}
+
+/*
+======================
+SV_FixCheckBottom
+
+======================
+*/
+void SV_FixCheckBottom (edict_t *ent)
+{
+	ent->flags |= FL_PARTIALGROUND;
+}
+
+
+
+/*
+================
+SV_NewChaseDir
+
+================
+*/
+#define	DI_NODIR	-1
+void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist)
+{
+	float	deltax,deltay;
+	float	d[3];
+	float	tdir, olddir, turnaround;
+
+	//FIXME: how did we get here with no enemy
+	if (!enemy)
+		return;
+
+	olddir = anglemod( (int)(actor->ideal_yaw/45)*45 );
+	turnaround = anglemod(olddir - 180);
+
+	deltax = enemy->s.origin[0] - actor->s.origin[0];
+	deltay = enemy->s.origin[1] - actor->s.origin[1];
+	if (deltax>10)
+		d[1]= 0;
+	else if (deltax<-10)
+		d[1]= 180;
+	else
+		d[1]= DI_NODIR;
+	if (deltay<-10)
+		d[2]= 270;
+	else if (deltay>10)
+		d[2]= 90;
+	else
+		d[2]= DI_NODIR;
+
+// try direct route
+	if (d[1] != DI_NODIR && d[2] != DI_NODIR)
+	{
+		if (d[1] == 0)
+			tdir = d[2] == 90 ? 45 : 315;
+		else
+			tdir = d[2] == 90 ? 135 : 215;
+			
+		if (tdir != turnaround && SV_StepDirection(actor, tdir, dist))
+			return;
+	}
+
+// try other directions
+	if ( ((rand()&3) & 1) ||  abs(deltay)>abs(deltax))
+	{
+		tdir=d[1];
+		d[1]=d[2];
+		d[2]=tdir;
+	}
+
+	if (d[1]!=DI_NODIR && d[1]!=turnaround 
+	&& SV_StepDirection(actor, d[1], dist))
+			return;
+
+	if (d[2]!=DI_NODIR && d[2]!=turnaround
+	&& SV_StepDirection(actor, d[2], dist))
+			return;
+
+/* there is no direct path to the player, so pick another direction */
+
+	if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist))
+			return;
+
+	if (rand()&1) 	/*randomly determine direction of search*/
+	{
+		for (tdir=0 ; tdir<=315 ; tdir += 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+	else
+	{
+		for (tdir=315 ; tdir >=0 ; tdir -= 45)
+			if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) )
+					return;
+	}
+
+	if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) )
+			return;
+
+	actor->ideal_yaw = olddir;		// can't move
+
+// if a bridge was pulled out from underneath a monster, it may not have
+// a valid standing position at all
+
+	if (!M_CheckBottom (actor))
+		SV_FixCheckBottom (actor);
+}
+
+/*
+======================
+SV_CloseEnough
+
+======================
+*/
+qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist)
+{
+	int		i;
+	
+	for (i=0 ; i<3 ; i++)
+	{
+		if (goal->absmin[i] > ent->absmax[i] + dist)
+			return false;
+		if (goal->absmax[i] < ent->absmin[i] - dist)
+			return false;
+	}
+	return true;
+}
+
+
+/*
+======================
+M_MoveToGoal
+======================
+*/
+void M_MoveToGoal (edict_t *ent, float dist)
+{
+	edict_t		*goal;
+	
+	goal = ent->goalentity;
+
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return;
+
+// if the next step hits the enemy, return immediately
+	if (ent->enemy &&  SV_CloseEnough (ent, ent->enemy, dist) )
+		return;
+
+// bump around...
+	if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist))
+	{
+		if (ent->inuse)
+			SV_NewChaseDir (ent, goal, dist);
+	}
+}
+
+
+/*
+===============
+M_walkmove
+===============
+*/
+qboolean M_walkmove (edict_t *ent, float yaw, float dist)
+{
+	vec3_t	move;
+	
+	if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM)))
+		return false;
+
+	yaw = yaw*M_PI*2 / 360;
+	
+	move[0] = cos(yaw)*dist;
+	move[1] = sin(yaw)*dist;
+	move[2] = 0;
+
+	return SV_movestep(ent, move, true);
+}
--- /dev/null
+++ b/xatrix/m_mutant.c
@@ -1,0 +1,640 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_mutant.h"
+
+
+static int	sound_swing;
+static int	sound_hit;
+static int	sound_hit2;
+static int	sound_death;
+static int	sound_idle;
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_sight;
+static int	sound_search;
+static int	sound_step1;
+static int	sound_step2;
+static int	sound_step3;
+static int	sound_thud;
+
+//
+// SOUNDS
+//
+
+void mutant_step (edict_t *self)
+{
+	int		n;
+	n = (rand() + 1) % 3;
+	if (n == 0)
+		gi.sound (self, CHAN_VOICE, sound_step1, 1, ATTN_NORM, 0);		
+	else if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_step2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_step3, 1, ATTN_NORM, 0);
+}
+
+void mutant_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void mutant_search (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
+}
+
+void mutant_swing (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
+}
+
+
+//
+// STAND
+//
+
+mframe_t mutant_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 10
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 20
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 30
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 40
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,		// 50
+
+	ai_stand, 0, NULL
+};
+mmove_t mutant_move_stand = {FRAME_stand101, FRAME_stand151, mutant_frames_stand, NULL};
+
+void mutant_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_stand;
+}
+
+
+//
+// IDLE
+//
+
+void mutant_idle_loop (edict_t *self)
+{
+	if (qrandom() < 0.75)
+		self->monsterinfo.nextframe = FRAME_stand155;
+}
+
+mframe_t mutant_frames_idle [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,					// scratch loop start
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, mutant_idle_loop,		// scratch loop end
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t mutant_move_idle = {FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand};
+
+void mutant_idle (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_idle;
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//
+// WALK
+//
+
+void mutant_walk (edict_t *self);
+
+mframe_t mutant_frames_walk [] =
+{
+	ai_walk,	3,		NULL,
+	ai_walk,	1,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	10,		NULL,
+	ai_walk,	13,		NULL,
+	ai_walk,	10,		NULL,
+	ai_walk,	0,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	6,		NULL,
+	ai_walk,	16,		NULL,
+	ai_walk,	15,		NULL,
+	ai_walk,	6,		NULL
+};
+mmove_t mutant_move_walk = {FRAME_walk05, FRAME_walk16, mutant_frames_walk, NULL};
+
+void mutant_walk_loop (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_walk;
+}
+
+mframe_t mutant_frames_start_walk [] =
+{
+	ai_walk,	5,		NULL,
+	ai_walk,	5,		NULL,
+	ai_walk,	-2,		NULL,
+	ai_walk,	1,		NULL
+};
+mmove_t mutant_move_start_walk = {FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop};
+
+void mutant_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_start_walk;
+}
+
+
+//
+// RUN
+//
+
+mframe_t mutant_frames_run [] =
+{
+	ai_run,	40,		NULL,
+	ai_run,	40,		mutant_step,
+	ai_run,	24,		NULL,
+	ai_run,	5,		mutant_step,
+	ai_run,	17,		NULL,
+	ai_run,	10,		NULL
+};
+mmove_t mutant_move_run = {FRAME_run03, FRAME_run08, mutant_frames_run, NULL};
+
+void mutant_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &mutant_move_stand;
+	else
+		self->monsterinfo.currentmove = &mutant_move_run;
+}
+
+
+//
+// MELEE
+//
+
+void mutant_hit_left (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->mins[0], 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void mutant_hit_right (edict_t *self)
+{
+	vec3_t	aim;
+
+	VectorSet (aim, MELEE_DISTANCE, self->maxs[0], 8);
+	if (fire_hit (self, aim, (10 + (rand() %5)), 100))
+		gi.sound (self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
+}
+
+void mutant_check_refire (edict_t *self)
+{
+	if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attack09;
+}
+
+mframe_t mutant_frames_attack [] =
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	mutant_hit_left,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	mutant_hit_right,
+	ai_charge,	0,	mutant_check_refire
+};
+mmove_t mutant_move_attack = {FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run};
+
+void mutant_melee (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_attack;
+}
+
+
+//
+// ATTACK
+//
+
+void mutant_jump_touch (edict_t *self, edict_t *other, cplane_t *, csurface_t *)
+{
+	if (self->health <= 0)
+	{
+		self->touch = NULL;
+		return;
+	}
+
+	if (other->takedamage)
+	{
+		if (VectorLength(self->velocity) > 400)
+		{
+			vec3_t	point;
+			vec3_t	normal;
+			int		damage;
+
+			VectorCopy (self->velocity, normal);
+			VectorNormalize(normal);
+			VectorMA (self->s.origin, self->maxs[0], normal, point);
+			damage = 40 + 10 * qrandom();
+			T_Damage (other, self, self, self->velocity, point, normal, damage, damage, 0, MOD_UNKNOWN);
+		}
+	}
+
+	if (!M_CheckBottom (self))
+	{
+		if (self->groundentity)
+		{
+			self->monsterinfo.nextframe = FRAME_attack02;
+			self->touch = NULL;
+		}
+		return;
+	}
+
+	self->touch = NULL;
+}
+
+void mutant_jump_takeoff (edict_t *self)
+{
+	vec3_t	forward;
+
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+	AngleVectors (self->s.angles, forward, NULL, NULL);
+	self->s.origin[2] += 1;
+	VectorScale (forward, 600, self->velocity);
+	self->velocity[2] = 250;
+	self->groundentity = NULL;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->monsterinfo.attack_finished = level.time + 3;
+	self->touch = mutant_jump_touch;
+}
+
+void mutant_check_landing (edict_t *self)
+{
+	if (self->groundentity)
+	{
+		gi.sound (self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
+		self->monsterinfo.attack_finished = 0;
+		self->monsterinfo.aiflags &= ~AI_DUCKED;
+		return;
+	}
+
+	if (level.time > self->monsterinfo.attack_finished)
+		self->monsterinfo.nextframe = FRAME_attack02;
+	else
+		self->monsterinfo.nextframe = FRAME_attack05;
+}
+
+mframe_t mutant_frames_jump [] =
+{
+	ai_charge,	 0,	NULL,
+	ai_charge,	17,	NULL,
+	ai_charge,	15,	mutant_jump_takeoff,
+	ai_charge,	15,	NULL,
+	ai_charge,	15,	mutant_check_landing,
+	ai_charge,	 0,	NULL,
+	ai_charge,	 3,	NULL,
+	ai_charge,	 0,	NULL
+};
+mmove_t mutant_move_jump = {FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run};
+
+void mutant_jump (edict_t *self)
+{
+	self->monsterinfo.currentmove = &mutant_move_jump;
+}
+
+
+//
+// CHECKATTACK
+//
+
+qboolean mutant_check_melee (edict_t *self)
+{
+	if (range (self, self->enemy) == RANGE_MELEE)
+		return true;
+	return false;
+}
+
+qboolean mutant_check_jump (edict_t *self)
+{
+	vec3_t	v;
+	float	distance;
+
+	if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
+		return false;
+
+	if (self->absmax[2] < (self->enemy->absmin[2] + 0.25 * self->enemy->size[2]))
+		return false;
+
+	v[0] = self->s.origin[0] - self->enemy->s.origin[0];
+	v[1] = self->s.origin[1] - self->enemy->s.origin[1];
+	v[2] = 0;
+	distance = VectorLength(v);
+
+	if (distance < 100)
+		return false;
+	if (distance > 100)
+	{
+		if (qrandom() < 0.9)
+			return false;
+	}
+
+	return true;
+}
+
+qboolean mutant_checkattack (edict_t *self)
+{
+	if (!self->enemy || self->enemy->health <= 0)
+		return false;
+
+	if (mutant_check_melee(self))
+	{
+		self->monsterinfo.attack_state = AS_MELEE;
+		return true;
+	}
+
+	if (mutant_check_jump(self))
+	{
+		self->monsterinfo.attack_state = AS_MISSILE;
+		// FIXME play a jump sound here
+		return true;
+	}
+
+	return false;
+}
+
+
+//
+// PAIN
+//
+
+mframe_t mutant_frames_pain1 [] =
+{
+	ai_move,	4,	NULL,
+	ai_move,	-3,	NULL,
+	ai_move,	-8,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	5,	NULL
+};
+mmove_t mutant_move_pain1 = {FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run};
+
+mframe_t mutant_frames_pain2 [] =
+{
+	ai_move,	-24,NULL,
+	ai_move,	11,	NULL,
+	ai_move,	5,	NULL,
+	ai_move,	-2,	NULL,
+	ai_move,	6,	NULL,
+	ai_move,	4,	NULL
+};
+mmove_t mutant_move_pain2 = {FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run};
+
+mframe_t mutant_frames_pain3 [] =
+{
+	ai_move,	-22,NULL,
+	ai_move,	3,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	1,	NULL,
+	ai_move,	6,	NULL,
+	ai_move,	3,	NULL,
+	ai_move,	2,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	1,	NULL
+};
+mmove_t mutant_move_pain3 = {FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run};
+
+void mutant_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+	if (r < 0.33)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain1;
+	}
+	else if (r < 0.66)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+		self->monsterinfo.currentmove = &mutant_move_pain3;
+	}
+}
+
+
+//
+// DEATH
+//
+
+void mutant_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	gi.linkentity (self);
+
+	M_FlyCheck (self);
+}
+
+mframe_t mutant_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t mutant_move_death1 = {FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead};
+
+mframe_t mutant_frames_death2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t mutant_move_death2 = {FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead};
+
+void mutant_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum = 1;
+
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &mutant_move_death1;
+	else
+		self->monsterinfo.currentmove = &mutant_move_death2;
+}
+
+
+//
+// SPAWN
+//
+
+/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_mutant (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_swing = gi.soundindex ("mutant/mutatck1.wav");
+	sound_hit = gi.soundindex ("mutant/mutatck2.wav");
+	sound_hit2 = gi.soundindex ("mutant/mutatck3.wav");
+	sound_death = gi.soundindex ("mutant/mutdeth1.wav");
+	sound_idle = gi.soundindex ("mutant/mutidle1.wav");
+	sound_pain1 = gi.soundindex ("mutant/mutpain1.wav");
+	sound_pain2 = gi.soundindex ("mutant/mutpain2.wav");
+	sound_sight = gi.soundindex ("mutant/mutsght1.wav");
+	sound_search = gi.soundindex ("mutant/mutsrch1.wav");
+	sound_step1 = gi.soundindex ("mutant/step1.wav");
+	sound_step2 = gi.soundindex ("mutant/step2.wav");
+	sound_step3 = gi.soundindex ("mutant/step3.wav");
+	sound_thud = gi.soundindex ("mutant/thud1.wav");
+	
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/mutant/tris.md2");
+	VectorSet (self->mins, -32, -32, -24);
+	VectorSet (self->maxs, 32, 32, 48);
+
+	self->health = 300;
+	self->gib_health = -120;
+	self->mass = 300;
+
+	self->pain = mutant_pain;
+	self->die = mutant_die;
+
+	self->monsterinfo.stand = mutant_stand;
+	self->monsterinfo.walk = mutant_walk;
+	self->monsterinfo.run = mutant_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = mutant_jump;
+	self->monsterinfo.melee = mutant_melee;
+	self->monsterinfo.sight = mutant_sight;
+	self->monsterinfo.search = mutant_search;
+	self->monsterinfo.idle = mutant_idle;
+	self->monsterinfo.checkattack = mutant_checkattack;
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &mutant_move_stand;
+
+	self->monsterinfo.scale = MODEL_SCALE;
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_mutant.h
@@ -1,0 +1,155 @@
+// G:\quake2\baseq2\models/monsters/mutant
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attack01        	0
+#define FRAME_attack02        	1
+#define FRAME_attack03        	2
+#define FRAME_attack04        	3
+#define FRAME_attack05        	4
+#define FRAME_attack06        	5
+#define FRAME_attack07        	6
+#define FRAME_attack08        	7
+#define FRAME_attack09        	8
+#define FRAME_attack10        	9
+#define FRAME_attack11        	10
+#define FRAME_attack12        	11
+#define FRAME_attack13        	12
+#define FRAME_attack14        	13
+#define FRAME_attack15        	14
+#define FRAME_death101        	15
+#define FRAME_death102        	16
+#define FRAME_death103        	17
+#define FRAME_death104        	18
+#define FRAME_death105        	19
+#define FRAME_death106        	20
+#define FRAME_death107        	21
+#define FRAME_death108        	22
+#define FRAME_death109        	23
+#define FRAME_death201        	24
+#define FRAME_death202        	25
+#define FRAME_death203        	26
+#define FRAME_death204        	27
+#define FRAME_death205        	28
+#define FRAME_death206        	29
+#define FRAME_death207        	30
+#define FRAME_death208        	31
+#define FRAME_death209        	32
+#define FRAME_death210        	33
+#define FRAME_pain101         	34
+#define FRAME_pain102         	35
+#define FRAME_pain103         	36
+#define FRAME_pain104         	37
+#define FRAME_pain105         	38
+#define FRAME_pain201         	39
+#define FRAME_pain202         	40
+#define FRAME_pain203         	41
+#define FRAME_pain204         	42
+#define FRAME_pain205         	43
+#define FRAME_pain206         	44
+#define FRAME_pain301         	45
+#define FRAME_pain302         	46
+#define FRAME_pain303         	47
+#define FRAME_pain304         	48
+#define FRAME_pain305         	49
+#define FRAME_pain306         	50
+#define FRAME_pain307         	51
+#define FRAME_pain308         	52
+#define FRAME_pain309         	53
+#define FRAME_pain310         	54
+#define FRAME_pain311         	55
+#define FRAME_run03           	56
+#define FRAME_run04           	57
+#define FRAME_run05           	58
+#define FRAME_run06           	59
+#define FRAME_run07           	60
+#define FRAME_run08           	61
+#define FRAME_stand101        	62
+#define FRAME_stand102        	63
+#define FRAME_stand103        	64
+#define FRAME_stand104        	65
+#define FRAME_stand105        	66
+#define FRAME_stand106        	67
+#define FRAME_stand107        	68
+#define FRAME_stand108        	69
+#define FRAME_stand109        	70
+#define FRAME_stand110        	71
+#define FRAME_stand111        	72
+#define FRAME_stand112        	73
+#define FRAME_stand113        	74
+#define FRAME_stand114        	75
+#define FRAME_stand115        	76
+#define FRAME_stand116        	77
+#define FRAME_stand117        	78
+#define FRAME_stand118        	79
+#define FRAME_stand119        	80
+#define FRAME_stand120        	81
+#define FRAME_stand121        	82
+#define FRAME_stand122        	83
+#define FRAME_stand123        	84
+#define FRAME_stand124        	85
+#define FRAME_stand125        	86
+#define FRAME_stand126        	87
+#define FRAME_stand127        	88
+#define FRAME_stand128        	89
+#define FRAME_stand129        	90
+#define FRAME_stand130        	91
+#define FRAME_stand131        	92
+#define FRAME_stand132        	93
+#define FRAME_stand133        	94
+#define FRAME_stand134        	95
+#define FRAME_stand135        	96
+#define FRAME_stand136        	97
+#define FRAME_stand137        	98
+#define FRAME_stand138        	99
+#define FRAME_stand139        	100
+#define FRAME_stand140        	101
+#define FRAME_stand141        	102
+#define FRAME_stand142        	103
+#define FRAME_stand143        	104
+#define FRAME_stand144        	105
+#define FRAME_stand145        	106
+#define FRAME_stand146        	107
+#define FRAME_stand147        	108
+#define FRAME_stand148        	109
+#define FRAME_stand149        	110
+#define FRAME_stand150        	111
+#define FRAME_stand151        	112
+#define FRAME_stand152        	113
+#define FRAME_stand153        	114
+#define FRAME_stand154        	115
+#define FRAME_stand155        	116
+#define FRAME_stand156        	117
+#define FRAME_stand157        	118
+#define FRAME_stand158        	119
+#define FRAME_stand159        	120
+#define FRAME_stand160        	121
+#define FRAME_stand161        	122
+#define FRAME_stand162        	123
+#define FRAME_stand163        	124
+#define FRAME_stand164        	125
+#define FRAME_walk01          	126
+#define FRAME_walk02          	127
+#define FRAME_walk03          	128
+#define FRAME_walk04          	129
+#define FRAME_walk05          	130
+#define FRAME_walk06          	131
+#define FRAME_walk07          	132
+#define FRAME_walk08          	133
+#define FRAME_walk09          	134
+#define FRAME_walk10          	135
+#define FRAME_walk11          	136
+#define FRAME_walk12          	137
+#define FRAME_walk13          	138
+#define FRAME_walk14          	139
+#define FRAME_walk15          	140
+#define FRAME_walk16          	141
+#define FRAME_walk17          	142
+#define FRAME_walk18          	143
+#define FRAME_walk19          	144
+#define FRAME_walk20          	145
+#define FRAME_walk21          	146
+#define FRAME_walk22          	147
+#define FRAME_walk23          	148
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_parasite.c
@@ -1,0 +1,529 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_parasite.h"
+
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_die;
+static int	sound_launch;
+static int	sound_impact;
+static int	sound_suck;
+static int	sound_reelin;
+static int	sound_sight;
+static int	sound_tap;
+static int	sound_scratch;
+static int	sound_search;
+
+
+void parasite_stand (edict_t *self);
+void parasite_start_run (edict_t *self);
+void parasite_run (edict_t *self);
+void parasite_walk (edict_t *self);
+void parasite_start_walk (edict_t *self);
+void parasite_end_fidget (edict_t *self);
+void parasite_do_fidget (edict_t *self);
+void parasite_refidget (edict_t *self);
+
+
+void parasite_launch (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0);
+}
+
+void parasite_reel_in (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0);
+}
+
+void parasite_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
+}
+
+void parasite_tap (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_tap, 1, ATTN_IDLE, 0);
+}
+
+void parasite_scratch (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_scratch, 1, ATTN_IDLE, 0);
+}
+
+void parasite_search (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0);
+}
+
+
+mframe_t parasite_frames_start_fidget [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_start_fidget = {FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget};
+
+mframe_t parasite_frames_fidget [] =
+{	
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_fidget = {FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget};
+
+mframe_t parasite_frames_end_fidget [] =
+{
+	ai_stand, 0, parasite_scratch,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t parasite_move_end_fidget = {FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand};
+
+void parasite_end_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void parasite_do_fidget (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_fidget;
+}
+
+void parasite_refidget (edict_t *self)
+{ 
+	if (qrandom() <= 0.8)
+		self->monsterinfo.currentmove = &parasite_move_fidget;
+	else
+		self->monsterinfo.currentmove = &parasite_move_end_fidget;
+}
+
+void parasite_idle (edict_t *self)
+{ 
+	self->monsterinfo.currentmove = &parasite_move_start_fidget;
+}
+
+
+mframe_t parasite_frames_stand [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap,
+	ai_stand, 0, NULL,
+	ai_stand, 0, parasite_tap
+};
+mmove_t	parasite_move_stand = {FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand};
+
+void parasite_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_stand;
+}
+
+
+mframe_t parasite_frames_run [] =
+{
+	ai_run, 30, NULL,
+	ai_run, 30, NULL,
+	ai_run, 22, NULL,
+	ai_run, 19, NULL,
+	ai_run, 24, NULL,
+	ai_run, 28, NULL,
+	ai_run, 25, NULL
+};
+mmove_t parasite_move_run = {FRAME_run03, FRAME_run09, parasite_frames_run, NULL};
+
+mframe_t parasite_frames_start_run [] =
+{
+	ai_run, 0,	NULL,
+	ai_run, 30, NULL,
+};
+mmove_t parasite_move_start_run = {FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run};
+
+mframe_t parasite_frames_stop_run [] =
+{	
+	ai_run, 20, NULL,
+	ai_run, 20,	NULL,
+	ai_run, 12, NULL,
+	ai_run, 10, NULL,
+	ai_run, 0,  NULL,
+	ai_run, 0,  NULL
+};
+mmove_t parasite_move_stop_run = {FRAME_run10, FRAME_run15, parasite_frames_stop_run, NULL};
+
+void parasite_start_run (edict_t *self)
+{	
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &parasite_move_stand;
+	else
+		self->monsterinfo.currentmove = &parasite_move_start_run;
+}
+
+void parasite_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &parasite_move_stand;
+	else
+		self->monsterinfo.currentmove = &parasite_move_run;
+}
+
+
+mframe_t parasite_frames_walk [] =
+{
+	ai_walk, 30, NULL,
+	ai_walk, 30, NULL,
+	ai_walk, 22, NULL,
+	ai_walk, 19, NULL,
+	ai_walk, 24, NULL,
+	ai_walk, 28, NULL,
+	ai_walk, 25, NULL
+};
+mmove_t parasite_move_walk = {FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk};
+
+mframe_t parasite_frames_start_walk [] =
+{
+	ai_walk, 0,	NULL,
+	ai_walk, 30, parasite_walk
+};
+mmove_t parasite_move_start_walk = {FRAME_run01, FRAME_run02, parasite_frames_start_walk, NULL};
+
+mframe_t parasite_frames_stop_walk [] =
+{	
+	ai_walk, 20, NULL,
+	ai_walk, 20,	NULL,
+	ai_walk, 12, NULL,
+	ai_walk, 10, NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t parasite_move_stop_walk = {FRAME_run10, FRAME_run15, parasite_frames_stop_walk, NULL};
+
+void parasite_start_walk (edict_t *self)
+{	
+	self->monsterinfo.currentmove = &parasite_move_start_walk;
+}
+
+void parasite_walk (edict_t *self)
+{
+	self->monsterinfo.currentmove = &parasite_move_walk;
+}
+
+
+mframe_t parasite_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 0,	NULL,
+	ai_move, 6,	NULL,
+	ai_move, 16, NULL,
+	ai_move, -6, NULL,
+	ai_move, -7, NULL,
+	ai_move, 0, NULL
+};
+mmove_t parasite_move_pain1 = {FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run};
+
+void parasite_pain (edict_t *self, edict_t *, float, int)
+{
+	if (self->health < (self->max_health / 2))
+		self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+		return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
+
+	self->monsterinfo.currentmove = &parasite_move_pain1;
+}
+
+
+static qboolean parasite_drain_attack_ok (vec3_t start, vec3_t end)
+{
+	vec3_t	dir, angles;
+
+	// check for max distance
+	VectorSubtract (start, end, dir);
+	if (VectorLength(dir) > 256)
+		return false;
+
+	// check for min/max pitch
+	vectoangles (dir, angles);
+	if (angles[0] < -180)
+		angles[0] += 360;
+	if (fabs(angles[0]) > 30)
+		return false;
+
+	return true;
+}
+
+void parasite_drain_attack (edict_t *self)
+{
+	vec3_t	offset, start, f, r, end, dir;
+	trace_t	tr;
+	int damage;
+
+	AngleVectors (self->s.angles, f, r, NULL);
+	VectorSet (offset, 24, 0, 6);
+	G_ProjectSource (self->s.origin, offset, f, r, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	if (!parasite_drain_attack_ok(start, end))
+	{
+		end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
+		if (!parasite_drain_attack_ok(start, end))
+		{
+			end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
+			if (!parasite_drain_attack_ok(start, end))
+				return;
+		}
+	}
+	VectorCopy (self->enemy->s.origin, end);
+
+	tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT);
+	if (tr.ent != self->enemy)
+		return;
+
+	if (self->s.frame == FRAME_drain03)
+	{
+		damage = 5;
+		gi.sound (self->enemy, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0);
+	}
+	else
+	{
+		if (self->s.frame == FRAME_drain04)
+			gi.sound (self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0);
+		damage = 2;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_PARASITE_ATTACK);
+	gi.WriteShort (self - g_edicts);
+	gi.WritePosition (start);
+	gi.WritePosition (end);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	VectorSubtract (start, end, dir);
+	T_Damage (self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN);
+}
+
+mframe_t parasite_frames_drain [] =
+{
+	ai_charge, 0,	parasite_launch,
+	ai_charge, 0,	NULL,
+	ai_charge, 15,	parasite_drain_attack,			// Target hits
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, -2,  parasite_drain_attack,			// drain
+	ai_charge, -2,	parasite_drain_attack,			// drain
+	ai_charge, -3,	parasite_drain_attack,			// drain
+	ai_charge, -2,	parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_drain_attack,			// drain
+	ai_charge, -1,  parasite_drain_attack,			// drain
+	ai_charge, 0,	parasite_reel_in,				// let go
+	ai_charge, -2,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 0,	NULL
+};
+mmove_t parasite_move_drain = {FRAME_drain01, FRAME_drain18, parasite_frames_drain, parasite_start_run};
+
+
+mframe_t parasite_frames_break [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 2,	NULL,
+	ai_charge, -3,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 1,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 3,	NULL,
+	ai_charge, 9,	NULL,
+	ai_charge, 6,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 8,	NULL,
+	ai_charge, 9,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -18,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,		// airborne
+	ai_charge, 0,	NULL,		// airborne
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 0,	NULL,		// slides
+	ai_charge, 4,	NULL,
+	ai_charge, 11,	NULL,		
+	ai_charge, -2,	NULL,
+	ai_charge, -5,	NULL,
+	ai_charge, 1,	NULL
+};
+mmove_t parasite_move_break = {FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run};
+
+/*
+=== 
+Break Stuff Ends
+===
+*/
+
+void parasite_attack (edict_t *self)
+{
+//	if (random() <= 0.2)
+//		self->monsterinfo.currentmove = &parasite_move_break;
+//	else
+		self->monsterinfo.currentmove = &parasite_move_drain;
+}
+
+
+
+/*
+===
+Death Stuff Starts
+===
+*/
+
+void parasite_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t parasite_frames_death [] =
+{
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL,
+	ai_move, 0,	 NULL
+};
+mmove_t parasite_move_death = {FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead};
+
+void parasite_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 2; n++)
+			ThrowGib (self, "models/objects/gibs/bone/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.currentmove = &parasite_move_death;
+}
+
+/*
+===
+End Death Stuff
+===
+*/
+
+/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_parasite (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("parasite/parpain1.wav");	
+	sound_pain2 = gi.soundindex ("parasite/parpain2.wav");	
+	sound_die = gi.soundindex ("parasite/pardeth1.wav");	
+	sound_launch = gi.soundindex("parasite/paratck1.wav");
+	sound_impact = gi.soundindex("parasite/paratck2.wav");
+	sound_suck = gi.soundindex("parasite/paratck3.wav");
+	sound_reelin = gi.soundindex("parasite/paratck4.wav");
+	sound_sight = gi.soundindex("parasite/parsght1.wav");
+	sound_tap = gi.soundindex("parasite/paridle1.wav");
+	sound_scratch = gi.soundindex("parasite/paridle2.wav");
+	sound_search = gi.soundindex("parasite/parsrch1.wav");
+
+	self->s.modelindex = gi.modelindex ("models/monsters/parasite/tris.md2");
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 24);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	self->health = 175;
+	self->gib_health = -50;
+	self->mass = 250;
+
+	self->pain = parasite_pain;
+	self->die = parasite_die;
+
+	self->monsterinfo.stand = parasite_stand;
+	self->monsterinfo.walk = parasite_start_walk;
+	self->monsterinfo.run = parasite_start_run;
+	self->monsterinfo.attack = parasite_attack;
+	self->monsterinfo.sight = parasite_sight;
+	self->monsterinfo.idle = parasite_idle;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.currentmove = &parasite_move_stand;	
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start (self);
+}
--- /dev/null
+++ b/xatrix/m_parasite.h
@@ -1,0 +1,124 @@
+// G:\quake2\baseq2\models/monsters/parasite
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_break01           0
+#define FRAME_break02           1
+#define FRAME_break03           2
+#define FRAME_break04           3
+#define FRAME_break05           4
+#define FRAME_break06           5
+#define FRAME_break07           6
+#define FRAME_break08           7
+#define FRAME_break09           8
+#define FRAME_break10           9
+#define FRAME_break11           10
+#define FRAME_break12           11
+#define FRAME_break13           12
+#define FRAME_break14           13
+#define FRAME_break15           14
+#define FRAME_break16           15
+#define FRAME_break17           16
+#define FRAME_break18           17
+#define FRAME_break19           18
+#define FRAME_break20           19
+#define FRAME_break21           20
+#define FRAME_break22           21
+#define FRAME_break23           22
+#define FRAME_break24           23
+#define FRAME_break25           24
+#define FRAME_break26           25
+#define FRAME_break27           26
+#define FRAME_break28           27
+#define FRAME_break29           28
+#define FRAME_break30           29
+#define FRAME_break31           30
+#define FRAME_break32           31
+#define FRAME_death101          32
+#define FRAME_death102          33
+#define FRAME_death103          34
+#define FRAME_death104          35
+#define FRAME_death105          36
+#define FRAME_death106          37
+#define FRAME_death107          38
+#define FRAME_drain01           39
+#define FRAME_drain02           40
+#define FRAME_drain03           41
+#define FRAME_drain04           42
+#define FRAME_drain05           43
+#define FRAME_drain06           44
+#define FRAME_drain07           45
+#define FRAME_drain08           46
+#define FRAME_drain09           47
+#define FRAME_drain10           48
+#define FRAME_drain11           49
+#define FRAME_drain12           50
+#define FRAME_drain13           51
+#define FRAME_drain14           52
+#define FRAME_drain15           53
+#define FRAME_drain16           54
+#define FRAME_drain17           55
+#define FRAME_drain18           56
+#define FRAME_pain101           57
+#define FRAME_pain102           58
+#define FRAME_pain103           59
+#define FRAME_pain104           60
+#define FRAME_pain105           61
+#define FRAME_pain106           62
+#define FRAME_pain107           63
+#define FRAME_pain108           64
+#define FRAME_pain109           65
+#define FRAME_pain110           66
+#define FRAME_pain111           67
+#define FRAME_run01             68
+#define FRAME_run02             69
+#define FRAME_run03             70
+#define FRAME_run04             71
+#define FRAME_run05             72
+#define FRAME_run06             73
+#define FRAME_run07             74
+#define FRAME_run08             75
+#define FRAME_run09             76
+#define FRAME_run10             77
+#define FRAME_run11             78
+#define FRAME_run12             79
+#define FRAME_run13             80
+#define FRAME_run14             81
+#define FRAME_run15             82
+#define FRAME_stand01           83
+#define FRAME_stand02           84
+#define FRAME_stand03           85
+#define FRAME_stand04           86
+#define FRAME_stand05           87
+#define FRAME_stand06           88
+#define FRAME_stand07           89
+#define FRAME_stand08           90
+#define FRAME_stand09           91
+#define FRAME_stand10           92
+#define FRAME_stand11           93
+#define FRAME_stand12           94
+#define FRAME_stand13           95
+#define FRAME_stand14           96
+#define FRAME_stand15           97
+#define FRAME_stand16           98
+#define FRAME_stand17           99
+#define FRAME_stand18           100
+#define FRAME_stand19           101
+#define FRAME_stand20           102
+#define FRAME_stand21           103
+#define FRAME_stand22           104
+#define FRAME_stand23           105
+#define FRAME_stand24           106
+#define FRAME_stand25           107
+#define FRAME_stand26           108
+#define FRAME_stand27           109
+#define FRAME_stand28           110
+#define FRAME_stand29           111
+#define FRAME_stand30           112
+#define FRAME_stand31           113
+#define FRAME_stand32           114
+#define FRAME_stand33           115
+#define FRAME_stand34           116
+#define FRAME_stand35           117
+
+#define MODEL_SCALE             1.000000
--- /dev/null
+++ b/xatrix/m_player.h
@@ -1,0 +1,205 @@
+// G:\quake2\baseq2\models/player_x/frames
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_stand31         	30
+#define FRAME_stand32         	31
+#define FRAME_stand33         	32
+#define FRAME_stand34         	33
+#define FRAME_stand35         	34
+#define FRAME_stand36         	35
+#define FRAME_stand37         	36
+#define FRAME_stand38         	37
+#define FRAME_stand39         	38
+#define FRAME_stand40         	39
+#define FRAME_run1            	40
+#define FRAME_run2            	41
+#define FRAME_run3            	42
+#define FRAME_run4            	43
+#define FRAME_run5            	44
+#define FRAME_run6            	45
+#define FRAME_attack1         	46
+#define FRAME_attack2         	47
+#define FRAME_attack3         	48
+#define FRAME_attack4         	49
+#define FRAME_attack5         	50
+#define FRAME_attack6         	51
+#define FRAME_attack7         	52
+#define FRAME_attack8         	53
+#define FRAME_pain101         	54
+#define FRAME_pain102         	55
+#define FRAME_pain103         	56
+#define FRAME_pain104         	57
+#define FRAME_pain201         	58
+#define FRAME_pain202         	59
+#define FRAME_pain203         	60
+#define FRAME_pain204         	61
+#define FRAME_pain301         	62
+#define FRAME_pain302         	63
+#define FRAME_pain303         	64
+#define FRAME_pain304         	65
+#define FRAME_jump1           	66
+#define FRAME_jump2           	67
+#define FRAME_jump3           	68
+#define FRAME_jump4           	69
+#define FRAME_jump5           	70
+#define FRAME_jump6           	71
+#define FRAME_flip01          	72
+#define FRAME_flip02          	73
+#define FRAME_flip03          	74
+#define FRAME_flip04          	75
+#define FRAME_flip05          	76
+#define FRAME_flip06          	77
+#define FRAME_flip07          	78
+#define FRAME_flip08          	79
+#define FRAME_flip09          	80
+#define FRAME_flip10          	81
+#define FRAME_flip11          	82
+#define FRAME_flip12          	83
+#define FRAME_salute01        	84
+#define FRAME_salute02        	85
+#define FRAME_salute03        	86
+#define FRAME_salute04        	87
+#define FRAME_salute05        	88
+#define FRAME_salute06        	89
+#define FRAME_salute07        	90
+#define FRAME_salute08        	91
+#define FRAME_salute09        	92
+#define FRAME_salute10        	93
+#define FRAME_salute11        	94
+#define FRAME_taunt01         	95
+#define FRAME_taunt02         	96
+#define FRAME_taunt03         	97
+#define FRAME_taunt04         	98
+#define FRAME_taunt05         	99
+#define FRAME_taunt06         	100
+#define FRAME_taunt07         	101
+#define FRAME_taunt08         	102
+#define FRAME_taunt09         	103
+#define FRAME_taunt10         	104
+#define FRAME_taunt11         	105
+#define FRAME_taunt12         	106
+#define FRAME_taunt13         	107
+#define FRAME_taunt14         	108
+#define FRAME_taunt15         	109
+#define FRAME_taunt16         	110
+#define FRAME_taunt17         	111
+#define FRAME_wave01          	112
+#define FRAME_wave02          	113
+#define FRAME_wave03          	114
+#define FRAME_wave04          	115
+#define FRAME_wave05          	116
+#define FRAME_wave06          	117
+#define FRAME_wave07          	118
+#define FRAME_wave08          	119
+#define FRAME_wave09          	120
+#define FRAME_wave10          	121
+#define FRAME_wave11          	122
+#define FRAME_point01         	123
+#define FRAME_point02         	124
+#define FRAME_point03         	125
+#define FRAME_point04         	126
+#define FRAME_point05         	127
+#define FRAME_point06         	128
+#define FRAME_point07         	129
+#define FRAME_point08         	130
+#define FRAME_point09         	131
+#define FRAME_point10         	132
+#define FRAME_point11         	133
+#define FRAME_point12         	134
+#define FRAME_crstnd01        	135
+#define FRAME_crstnd02        	136
+#define FRAME_crstnd03        	137
+#define FRAME_crstnd04        	138
+#define FRAME_crstnd05        	139
+#define FRAME_crstnd06        	140
+#define FRAME_crstnd07        	141
+#define FRAME_crstnd08        	142
+#define FRAME_crstnd09        	143
+#define FRAME_crstnd10        	144
+#define FRAME_crstnd11        	145
+#define FRAME_crstnd12        	146
+#define FRAME_crstnd13        	147
+#define FRAME_crstnd14        	148
+#define FRAME_crstnd15        	149
+#define FRAME_crstnd16        	150
+#define FRAME_crstnd17        	151
+#define FRAME_crstnd18        	152
+#define FRAME_crstnd19        	153
+#define FRAME_crwalk1         	154
+#define FRAME_crwalk2         	155
+#define FRAME_crwalk3         	156
+#define FRAME_crwalk4         	157
+#define FRAME_crwalk5         	158
+#define FRAME_crwalk6         	159
+#define FRAME_crattak1        	160
+#define FRAME_crattak2        	161
+#define FRAME_crattak3        	162
+#define FRAME_crattak4        	163
+#define FRAME_crattak5        	164
+#define FRAME_crattak6        	165
+#define FRAME_crattak7        	166
+#define FRAME_crattak8        	167
+#define FRAME_crattak9        	168
+#define FRAME_crpain1         	169
+#define FRAME_crpain2         	170
+#define FRAME_crpain3         	171
+#define FRAME_crpain4         	172
+#define FRAME_crdeath1        	173
+#define FRAME_crdeath2        	174
+#define FRAME_crdeath3        	175
+#define FRAME_crdeath4        	176
+#define FRAME_crdeath5        	177
+#define FRAME_death101        	178
+#define FRAME_death102        	179
+#define FRAME_death103        	180
+#define FRAME_death104        	181
+#define FRAME_death105        	182
+#define FRAME_death106        	183
+#define FRAME_death201        	184
+#define FRAME_death202        	185
+#define FRAME_death203        	186
+#define FRAME_death204        	187
+#define FRAME_death205        	188
+#define FRAME_death206        	189
+#define FRAME_death301        	190
+#define FRAME_death302        	191
+#define FRAME_death303        	192
+#define FRAME_death304        	193
+#define FRAME_death305        	194
+#define FRAME_death306        	195
+#define FRAME_death307        	196
+#define FRAME_death308        	197
+
+#define MODEL_SCALE		1.000000
+
--- /dev/null
+++ b/xatrix/m_rider.h
@@ -1,0 +1,66 @@
+// G:\quake2\baseq2\models/monsters/boss3/rider
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_stand201        	0
+#define FRAME_stand202        	1
+#define FRAME_stand203        	2
+#define FRAME_stand204        	3
+#define FRAME_stand205        	4
+#define FRAME_stand206        	5
+#define FRAME_stand207        	6
+#define FRAME_stand208        	7
+#define FRAME_stand209        	8
+#define FRAME_stand210        	9
+#define FRAME_stand211        	10
+#define FRAME_stand212        	11
+#define FRAME_stand213        	12
+#define FRAME_stand214        	13
+#define FRAME_stand215        	14
+#define FRAME_stand216        	15
+#define FRAME_stand217        	16
+#define FRAME_stand218        	17
+#define FRAME_stand219        	18
+#define FRAME_stand220        	19
+#define FRAME_stand221        	20
+#define FRAME_stand222        	21
+#define FRAME_stand223        	22
+#define FRAME_stand224        	23
+#define FRAME_stand225        	24
+#define FRAME_stand226        	25
+#define FRAME_stand227        	26
+#define FRAME_stand228        	27
+#define FRAME_stand229        	28
+#define FRAME_stand230        	29
+#define FRAME_stand231        	30
+#define FRAME_stand232        	31
+#define FRAME_stand233        	32
+#define FRAME_stand234        	33
+#define FRAME_stand235        	34
+#define FRAME_stand236        	35
+#define FRAME_stand237        	36
+#define FRAME_stand238        	37
+#define FRAME_stand239        	38
+#define FRAME_stand240        	39
+#define FRAME_stand241        	40
+#define FRAME_stand242        	41
+#define FRAME_stand243        	42
+#define FRAME_stand244        	43
+#define FRAME_stand245        	44
+#define FRAME_stand246        	45
+#define FRAME_stand247        	46
+#define FRAME_stand248        	47
+#define FRAME_stand249        	48
+#define FRAME_stand250        	49
+#define FRAME_stand251        	50
+#define FRAME_stand252        	51
+#define FRAME_stand253        	52
+#define FRAME_stand254        	53
+#define FRAME_stand255        	54
+#define FRAME_stand256        	55
+#define FRAME_stand257        	56
+#define FRAME_stand258        	57
+#define FRAME_stand259        	58
+#define FRAME_stand260        	59
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_soldier.c
@@ -1,0 +1,2597 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_soldier.h"
+
+
+static int	sound_idle;
+static int	sound_sight1;
+static int	sound_sight2;
+static int	sound_pain_light;
+static int	sound_pain;
+static int	sound_pain_ss;
+static int	sound_death_light;
+static int	sound_death;
+static int	sound_death_ss;
+static int	sound_cock;
+
+
+void soldier_idle (edict_t *self)
+{
+	if (qrandom() > 0.8)
+		gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void soldier_cock (edict_t *self)
+{
+	if (self->s.frame == FRAME_stand322)
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+}
+
+
+// STAND
+
+void soldier_stand (edict_t *self);
+
+mframe_t soldier_frames_stand1 [] =
+{
+	ai_stand, 0, soldier_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand1 = {FRAME_stand101, FRAME_stand130, soldier_frames_stand1, soldier_stand};
+
+mframe_t soldier_frames_stand3 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, soldier_cock,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand3 = {FRAME_stand301, FRAME_stand339, soldier_frames_stand3, soldier_stand};
+
+/*
+mframe_t soldier_frames_stand4 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 4, NULL,
+	ai_stand, 1, NULL,
+	ai_stand, -1, NULL,
+	ai_stand, -2, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldier_move_stand4 = {FRAME_stand401, FRAME_stand452, soldier_frames_stand4, NULL};
+*/
+
+void soldier_stand (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &soldier_move_stand3) || (qrandom() < 0.8))
+		self->monsterinfo.currentmove = &soldier_move_stand1;
+	else
+		self->monsterinfo.currentmove = &soldier_move_stand3;
+}
+
+
+//
+// WALK
+//
+
+void soldier_walk1_random (edict_t *self)
+{
+	if (qrandom() > 0.1)
+		self->monsterinfo.nextframe = FRAME_walk101;
+}
+
+mframe_t soldier_frames_walk1 [] =
+{
+	ai_walk, 3,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, -1, soldier_walk1_random,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t soldier_move_walk1 = {FRAME_walk101, FRAME_walk133, soldier_frames_walk1, NULL};
+
+mframe_t soldier_frames_walk2 [] =
+{
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 9,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 7,  NULL
+};
+mmove_t soldier_move_walk2 = {FRAME_walk209, FRAME_walk218, soldier_frames_walk2, NULL};
+
+void soldier_walk (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &soldier_move_walk1;
+	else
+		self->monsterinfo.currentmove = &soldier_move_walk2;
+}
+
+
+//
+// RUN
+//
+
+void soldier_run (edict_t *self);
+
+mframe_t soldier_frames_start_run [] =
+{
+	ai_run, 7,  NULL,
+	ai_run, 5,  NULL
+};
+mmove_t soldier_move_start_run = {FRAME_run01, FRAME_run02, soldier_frames_start_run, soldier_run};
+
+mframe_t soldier_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 11, NULL,
+	ai_run, 11, NULL,
+	ai_run, 16, NULL,
+	ai_run, 10, NULL,
+	ai_run, 15, NULL
+};
+mmove_t soldier_move_run = {FRAME_run03, FRAME_run08, soldier_frames_run, NULL};
+
+void soldier_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &soldier_move_stand1;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &soldier_move_walk1 ||
+		self->monsterinfo.currentmove == &soldier_move_walk2 ||
+		self->monsterinfo.currentmove == &soldier_move_start_run)
+	{
+		self->monsterinfo.currentmove = &soldier_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &soldier_move_start_run;
+	}
+}
+
+
+//
+// PAIN
+//
+
+mframe_t soldier_frames_pain1 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t soldier_move_pain1 = {FRAME_pain101, FRAME_pain105, soldier_frames_pain1, soldier_run};
+
+mframe_t soldier_frames_pain2 [] =
+{
+	ai_move, -13, NULL,
+	ai_move, -1,  NULL,
+	ai_move, 2,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL
+};
+mmove_t soldier_move_pain2 = {FRAME_pain201, FRAME_pain207, soldier_frames_pain2, soldier_run};
+
+mframe_t soldier_frames_pain3 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, 10, NULL,
+	ai_move, -4, NULL,
+	ai_move, -1, NULL,
+	ai_move, -3, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 4,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t soldier_move_pain3 = {FRAME_pain301, FRAME_pain318, soldier_frames_pain3, soldier_run};
+
+mframe_t soldier_frames_pain4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -6,  NULL,
+	ai_move, 8,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 5,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_pain4 = {FRAME_pain401, FRAME_pain417, soldier_frames_pain4, soldier_run};
+
+
+void soldier_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum |= 1;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3)))
+			self->monsterinfo.currentmove = &soldier_move_pain4;
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	n = self->s.skinnum | 1;
+	if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+	else if (n == 3)
+		gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+
+	if (self->velocity[2] > 100)
+	{
+		self->monsterinfo.currentmove = &soldier_move_pain4;
+		return;
+	}
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+
+	if (r < 0.33)
+		self->monsterinfo.currentmove = &soldier_move_pain1;
+	else if (r < 0.66)
+		self->monsterinfo.currentmove = &soldier_move_pain2;
+	else
+		self->monsterinfo.currentmove = &soldier_move_pain3;
+}
+
+
+//
+// ATTACK
+//
+
+static int blaster_flash [] = {MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8};
+static int shotgun_flash [] = {MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8};
+static int machinegun_flash [] = {MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, MZ2_SOLDIER_MACHINEGUN_8};
+
+void soldier_fire (edict_t *self, int flash_number)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	aim;
+	vec3_t	dir;
+	vec3_t	end;
+	float	r, u;
+	int		flash_index;
+
+	if (self->s.skinnum < 2)
+		flash_index = blaster_flash[flash_number];
+	else if (self->s.skinnum < 4)
+		flash_index = shotgun_flash[flash_number];
+	else
+		flash_index = machinegun_flash[flash_number];
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start);
+
+	if (flash_number == 5 || flash_number == 6)
+	{
+		VectorCopy (forward, aim);
+	}
+	else
+	{
+		VectorCopy (self->enemy->s.origin, end);
+		end[2] += self->enemy->viewheight;
+		VectorSubtract (end, start, aim);
+		vectoangles (aim, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*1000;
+		u = crandom()*500;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		VectorSubtract (end, start, aim);
+		VectorNormalize (aim);
+	}
+
+	if (self->s.skinnum <= 1)
+	{
+		monster_fire_blaster (self, start, aim, 5, 600, flash_index, EF_BLASTER);
+	}
+	else if (self->s.skinnum <= 3)
+	{
+		monster_fire_shotgun (self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index);
+	}
+	else
+	{
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME;
+
+		monster_fire_bullet (self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_index);
+
+		if (level.time >= self->monsterinfo.pausetime)
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		else
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+	}
+}
+
+// ATTACK1 (blaster/shotgun)
+
+void soldier_fire1 (edict_t *self)
+{
+	soldier_fire (self, 0);
+}
+
+void soldier_attack1_refire1 (edict_t *self)
+{
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+	else
+		self->monsterinfo.nextframe = FRAME_attak110;
+}
+
+void soldier_attack1_refire2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+}
+
+mframe_t soldier_frames_attack1 [] =
+{
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_fire1,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_attack1_refire1,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldier_cock,
+	ai_charge, 0,  soldier_attack1_refire2,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL
+};
+mmove_t soldier_move_attack1 = {FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run};
+
+// ATTACK2 (blaster/shotgun)
+
+void soldier_fire2 (edict_t *self)
+{
+	soldier_fire (self, 1);
+}
+
+void soldier_attack2_refire1 (edict_t *self)
+{
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak204;
+	else
+		self->monsterinfo.nextframe = FRAME_attak216;
+}
+
+void soldier_attack2_refire2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak204;
+}
+
+mframe_t soldier_frames_attack2 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack2_refire1,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_cock,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack2_refire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack2 = {FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run};
+
+// ATTACK3 (duck and shoot)
+
+void soldier_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void soldier_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+void soldier_fire3 (edict_t *self)
+{
+	soldier_duck_down (self);
+	soldier_fire (self, 2);
+}
+
+void soldier_attack3_refire (edict_t *self)
+{
+	if ((level.time + 0.4) < self->monsterinfo.pausetime)
+		self->monsterinfo.nextframe = FRAME_attak303;
+}
+
+mframe_t soldier_frames_attack3 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire3,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack3_refire,
+	ai_charge, 0, soldier_duck_up,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack3 = {FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run};
+
+// ATTACK4 (machinegun)
+
+void soldier_fire4 (edict_t *self)
+{
+	soldier_fire (self, 3);
+//
+//	if (self->enemy->health <= 0)
+//		return;
+//
+//	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+//		self->monsterinfo.nextframe = FRAME_attak402;
+}
+
+mframe_t soldier_frames_attack4 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire4,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldier_move_attack4 = {FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run};
+
+/*
+// ATTACK5 (prone)
+
+void soldier_fire5 (edict_t *self)
+{
+	soldier_fire (self, 4);
+}
+
+void soldier_attack5_refire (edict_t *self)
+{
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak505;
+}
+
+mframe_t soldier_frames_attack5 [] =
+{
+	ai_charge, 8, NULL,
+	ai_charge, 8, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_fire5,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldier_attack5_refire
+};
+mmove_t soldier_move_attack5 = {FRAME_attak501, FRAME_attak508, soldier_frames_attack5, soldier_run};
+*/
+
+// ATTACK6 (run & shoot)
+
+void soldier_fire8 (edict_t *self)
+{
+	soldier_fire (self, 7);
+}
+
+void soldier_attack6_refire (edict_t *self)
+{
+	if (self->enemy->health <= 0)
+		return;
+
+	if (range(self, self->enemy) < RANGE_MID)
+		return;
+
+	if (skill->value == 3)
+		self->monsterinfo.nextframe = FRAME_runs03;
+}
+
+mframe_t soldier_frames_attack6 [] =
+{
+	ai_charge, 10, NULL,
+	ai_charge,  4, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 11, soldier_fire8,
+	ai_charge, 13, NULL,
+	ai_charge, 18, NULL,
+	ai_charge, 15, NULL,
+	ai_charge, 14, NULL,
+	ai_charge, 11, NULL,
+	ai_charge,  8, NULL,
+	ai_charge, 11, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 17, soldier_attack6_refire
+};
+mmove_t soldier_move_attack6 = {FRAME_runs01, FRAME_runs14, soldier_frames_attack6, soldier_run};
+
+void soldier_attack(edict_t *self)
+{
+	if (self->s.skinnum < 4)
+	{
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &soldier_move_attack1;
+		else
+			self->monsterinfo.currentmove = &soldier_move_attack2;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &soldier_move_attack4;
+	}
+}
+
+
+//
+// SIGHT
+//
+
+void soldier_sight(edict_t *self, edict_t *)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+
+	if ((skill->value > 0) && (range(self, self->enemy) >= RANGE_MID))
+	{
+		if (qrandom() > 0.5)
+			self->monsterinfo.currentmove = &soldier_move_attack6;
+	}
+}
+
+//
+// DUCK
+//
+
+void soldier_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t soldier_frames_duck [] =
+{
+	ai_move, 5, soldier_duck_down,
+	ai_move, -1, soldier_duck_hold,
+	ai_move, 1,  NULL,
+	ai_move, 0,  soldier_duck_up,
+	ai_move, 5,  NULL
+};
+mmove_t soldier_move_duck = {FRAME_duck01, FRAME_duck05, soldier_frames_duck, soldier_run};
+
+void soldier_dodge (edict_t *self, edict_t *attacker, float eta)
+{
+	float	r;
+
+	r = qrandom();
+	if (r > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &soldier_move_duck;
+		return;
+	}
+
+	self->monsterinfo.pausetime = level.time + eta + 0.3;
+	r = qrandom();
+
+	if (skill->value == 1)
+	{
+		if (r > 0.33)
+			self->monsterinfo.currentmove = &soldier_move_duck;
+		else
+			self->monsterinfo.currentmove = &soldier_move_attack3;
+		return;
+	}
+
+	if (skill->value >= 2)
+	{
+		if (r > 0.66)
+			self->monsterinfo.currentmove = &soldier_move_duck;
+		else
+			self->monsterinfo.currentmove = &soldier_move_attack3;
+		return;
+	}
+
+	self->monsterinfo.currentmove = &soldier_move_attack3;
+}
+
+
+//
+// DEATH
+//
+
+void soldier_fire6 (edict_t *self)
+{
+	soldier_fire (self, 5);
+}
+
+void soldier_fire7 (edict_t *self)
+{
+	soldier_fire (self, 6);
+}
+
+void soldier_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t soldier_frames_death1 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldier_fire6,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldier_fire7,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death1 = {FRAME_death101, FRAME_death136, soldier_frames_death1, soldier_dead};
+
+mframe_t soldier_frames_death2 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death2 = {FRAME_death201, FRAME_death235, soldier_frames_death2, soldier_dead};
+
+mframe_t soldier_frames_death3 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+};
+mmove_t soldier_move_death3 = {FRAME_death301, FRAME_death345, soldier_frames_death3, soldier_dead};
+
+mframe_t soldier_frames_death4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death4 = {FRAME_death401, FRAME_death453, soldier_frames_death4, soldier_dead};
+
+mframe_t soldier_frames_death5 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death5 = {FRAME_death501, FRAME_death524, soldier_frames_death5, soldier_dead};
+
+mframe_t soldier_frames_death6 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldier_move_death6 = {FRAME_death601, FRAME_death610, soldier_frames_death6, soldier_dead};
+
+void soldier_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t point)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 3; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum |= 1;
+
+	if (self->s.skinnum == 1)
+		gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+	else if (self->s.skinnum == 3)
+		gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	else // (self->s.skinnum == 5)
+		gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+
+	if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+	{
+		// head shot
+		self->monsterinfo.currentmove = &soldier_move_death3;
+		return;
+	}
+
+	n = rand() % 5;
+	if (n == 0)
+		self->monsterinfo.currentmove = &soldier_move_death1;
+	else if (n == 1)
+		self->monsterinfo.currentmove = &soldier_move_death2;
+	else if (n == 2)
+		self->monsterinfo.currentmove = &soldier_move_death4;
+	else if (n == 3)
+		self->monsterinfo.currentmove = &soldier_move_death5;
+	else
+		self->monsterinfo.currentmove = &soldier_move_death6;
+}
+
+
+//
+// SPAWN
+//
+
+void SP_monster_soldier_x (edict_t *self)
+{
+
+	self->s.modelindex = gi.modelindex ("models/monsters/soldier/tris.md2");
+	self->monsterinfo.scale = MODEL_SCALE;
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	sound_idle =	gi.soundindex ("soldier/solidle1.wav");
+	sound_sight1 =	gi.soundindex ("soldier/solsght1.wav");
+	sound_sight2 =	gi.soundindex ("soldier/solsrch1.wav");
+	sound_cock =	gi.soundindex ("infantry/infatck3.wav");
+
+	self->mass = 100;
+
+	self->pain = soldier_pain;
+	self->die = soldier_die;
+
+	self->monsterinfo.stand = soldier_stand;
+	self->monsterinfo.walk = soldier_walk;
+	self->monsterinfo.run = soldier_run;
+	self->monsterinfo.dodge = soldier_dodge;
+	self->monsterinfo.attack = soldier_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = soldier_sight;
+
+	gi.linkentity (self);
+
+	self->monsterinfo.stand (self);
+
+	walkmonster_start (self);
+}
+
+
+/*QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier_light (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain_light = gi.soundindex ("soldier/solpain2.wav");
+	sound_death_light =	gi.soundindex ("soldier/soldeth2.wav");
+	gi.modelindex ("models/objects/laser/tris.md2");
+	gi.soundindex ("misc/lasfly.wav");
+	gi.soundindex ("soldier/solatck2.wav");
+
+	self->s.skinnum = 0;
+	self->health = 20;
+	self->gib_health = -30;
+}
+
+/*QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain = gi.soundindex ("soldier/solpain1.wav");
+	sound_death = gi.soundindex ("soldier/soldeth1.wav");
+	gi.soundindex ("soldier/solatck1.wav");
+
+	self->s.skinnum = 2;
+	self->health = 30;
+	self->gib_health = -30;
+}
+
+/*QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier_ss (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_x (self);
+
+	sound_pain_ss = gi.soundindex ("soldier/solpain3.wav");
+	sound_death_ss = gi.soundindex ("soldier/soldeth3.wav");
+	gi.soundindex ("soldier/solatck3.wav");
+
+	self->s.skinnum = 4;
+	self->health = 40;
+	self->gib_health = -30;
+}
+
+void soldierh_idle (edict_t *self)
+{
+	if (qrandom() > 0.8)
+		gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+void soldierh_cock (edict_t *self)
+{
+	if (self->s.frame == FRAME_stand322)
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0);
+	else
+		gi.sound (self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0);
+}
+
+
+// STAND
+
+void soldierh_stand (edict_t *self);
+
+mframe_t soldierh_frames_stand1 [] =
+{
+	ai_stand, 0, soldierh_idle,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldierh_move_stand1 = {FRAME_stand101, FRAME_stand130, soldierh_frames_stand1, soldierh_stand};
+
+mframe_t soldierh_frames_stand3 [] =
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, soldierh_cock,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t soldierh_move_stand3 = {FRAME_stand301, FRAME_stand339, soldierh_frames_stand3, soldierh_stand};
+
+
+void soldierh_stand (edict_t *self)
+{
+	if ((self->monsterinfo.currentmove == &soldierh_move_stand3) || (qrandom() < 0.8))
+		self->monsterinfo.currentmove = &soldierh_move_stand1;
+	else
+		self->monsterinfo.currentmove = &soldierh_move_stand3;
+}
+
+
+//
+// WALK
+//
+
+void soldierh_walk1_random (edict_t *self)
+{
+	if (qrandom() > 0.1)
+		self->monsterinfo.nextframe = FRAME_walk101;
+}
+
+mframe_t soldierh_frames_walk1 [] =
+{
+	ai_walk, 3,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 2,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, -1, soldierh_walk1_random,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL,
+	ai_walk, 0,  NULL
+};
+mmove_t soldierh_move_walk1 = {FRAME_walk101, FRAME_walk133, soldierh_frames_walk1, NULL};
+
+mframe_t soldierh_frames_walk2 [] =
+{
+	ai_walk, 4,  NULL,
+	ai_walk, 4,  NULL,
+	ai_walk, 9,  NULL,
+	ai_walk, 8,  NULL,
+	ai_walk, 5,  NULL,
+	ai_walk, 1,  NULL,
+	ai_walk, 3,  NULL,
+	ai_walk, 7,  NULL,
+	ai_walk, 6,  NULL,
+	ai_walk, 7,  NULL
+};
+mmove_t soldierh_move_walk2 = {FRAME_walk209, FRAME_walk218, soldierh_frames_walk2, NULL};
+
+void soldierh_walk (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		self->monsterinfo.currentmove = &soldierh_move_walk1;
+	else
+		self->monsterinfo.currentmove = &soldierh_move_walk2;
+}
+
+
+//
+// RUN
+//
+
+void soldierh_run (edict_t *self);
+
+mframe_t soldierh_frames_start_run [] =
+{
+	ai_run, 7,  NULL,
+	ai_run, 5,  NULL
+};
+mmove_t soldierh_move_start_run = {FRAME_run01, FRAME_run02, soldierh_frames_start_run, soldierh_run};
+
+mframe_t soldierh_frames_run [] =
+{
+	ai_run, 10, NULL,
+	ai_run, 11, NULL,
+	ai_run, 11, NULL,
+	ai_run, 16, NULL,
+	ai_run, 10, NULL,
+	ai_run, 15, NULL
+};
+mmove_t soldierh_move_run = {FRAME_run03, FRAME_run08, soldierh_frames_run, NULL};
+
+void soldierh_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &soldierh_move_stand1;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &soldierh_move_walk1 ||
+		self->monsterinfo.currentmove == &soldierh_move_walk2 ||
+		self->monsterinfo.currentmove == &soldierh_move_start_run)
+	{
+		self->monsterinfo.currentmove = &soldierh_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &soldierh_move_start_run;
+	}
+}
+
+
+//
+// PAIN
+//
+
+mframe_t soldierh_frames_pain1 [] =
+{
+	ai_move, -3, NULL,
+	ai_move, 4,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL
+};
+mmove_t soldierh_move_pain1 = {FRAME_pain101, FRAME_pain105, soldierh_frames_pain1, soldierh_run};
+
+mframe_t soldierh_frames_pain2 [] =
+{
+	ai_move, -13, NULL,
+	ai_move, -1,  NULL,
+	ai_move, 2,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL
+};
+mmove_t soldierh_move_pain2 = {FRAME_pain201, FRAME_pain207, soldierh_frames_pain2, soldierh_run};
+
+mframe_t soldierh_frames_pain3 [] =
+{
+	ai_move, -8, NULL,
+	ai_move, 10, NULL,
+	ai_move, -4, NULL,
+	ai_move, -1, NULL,
+	ai_move, -3, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 1,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 4,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 2,  NULL
+};
+mmove_t soldierh_move_pain3 = {FRAME_pain301, FRAME_pain318, soldierh_frames_pain3, soldierh_run};
+
+mframe_t soldierh_frames_pain4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -6,  NULL,
+	ai_move, 8,   NULL,
+	ai_move, 4,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 5,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_pain4 = {FRAME_pain401, FRAME_pain417, soldierh_frames_pain4, soldierh_run};
+
+void soldierh_pain (edict_t *self, edict_t *, float, int)
+{
+	float	r;
+	int		n;
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum |= 1;
+
+	if (level.time < self->pain_debounce_time)
+	{
+		if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldierh_move_pain1) || (self->monsterinfo.currentmove == &soldierh_move_pain2) || (self->monsterinfo.currentmove == &soldierh_move_pain3)))
+			self->monsterinfo.currentmove = &soldierh_move_pain4;
+		return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+
+	n = self->s.skinnum | 1;
+	if (n == 1)
+		gi.sound (self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0);
+	else if (n == 3)
+		gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0);
+
+	if (self->velocity[2] > 100)
+	{
+		self->monsterinfo.currentmove = &soldierh_move_pain4;
+		return;
+	}
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	r = qrandom();
+
+	if (r < 0.33)
+		self->monsterinfo.currentmove = &soldierh_move_pain1;
+	else if (r < 0.66)
+		self->monsterinfo.currentmove = &soldierh_move_pain2;
+	else
+		self->monsterinfo.currentmove = &soldierh_move_pain3;
+}
+
+
+//
+// ATTACK
+//
+
+extern void brain_dabeam (edict_t *self);
+
+void soldierh_laserbeam (edict_t *self, int flash_index)
+{
+ 
+	vec3_t forward, right, up;
+	vec3_t tempang, start;
+	vec3_t	dir, angles, end;
+	vec3_t	tempvec;
+	edict_t *ent;
+
+	// RAFAEL
+	// this sound can't be called this frequent
+	if (qrandom() > 0.8)
+		gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0);
+
+	VectorCopy (self->s.origin, start);
+	VectorCopy (self->enemy->s.origin, end);
+	VectorSubtract (end, start, dir);
+	vectoangles (dir, angles);
+	VectorCopy (monster_flash_offset[flash_index], tempvec);
+	
+	ent = G_Spawn ();
+	VectorCopy (self->s.origin, ent->s.origin);
+	VectorCopy (angles, tempang);
+	AngleVectors (tempang, forward, right, up);
+	VectorCopy (tempang, ent->s.angles);
+	VectorCopy (ent->s.origin, start);
+
+	if (flash_index == 85)
+	{
+		VectorMA (start, tempvec[0]-14, right, start);
+		VectorMA (start, tempvec[2]+8, up, start);
+		VectorMA (start, tempvec[1], forward, start);
+	}
+	else 
+	{
+		VectorMA (start, tempvec[0]+2, right, start);
+		VectorMA (start, tempvec[2]+8, up, start);
+		VectorMA (start, tempvec[1], forward, start);
+	}
+			
+	VectorCopy (start, ent->s.origin);
+	ent->enemy = self->enemy;
+	ent->owner = self;
+	
+	ent->dmg = 1;
+
+	monster_dabeam (ent);
+	
+}
+
+
+void soldierh_fire (edict_t *self, int flash_number)
+{
+	vec3_t	start;
+	vec3_t	forward, right, up;
+	vec3_t	aim;
+	vec3_t	dir;
+	vec3_t	end;
+	float	r, u;
+	int		flash_index;
+
+	if (self->s.skinnum < 2)
+		flash_index = blaster_flash[flash_number]; // ripper
+	else if (self->s.skinnum < 4)
+		flash_index = blaster_flash[flash_number]; // hyperblaster
+	else
+		flash_index = machinegun_flash[flash_number]; // laserbeam
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_index], forward, right, start);
+
+	if (flash_number == 5 || flash_number == 6)
+	{
+		VectorCopy (forward, aim);
+	}
+	else
+	{
+		VectorCopy (self->enemy->s.origin, end);
+		end[2] += self->enemy->viewheight;
+		VectorSubtract (end, start, aim);
+		vectoangles (aim, dir);
+		AngleVectors (dir, forward, right, up);
+
+		r = crandom()*100;
+		u = crandom()*50;
+		VectorMA (start, 8192, forward, end);
+		VectorMA (end, r, right, end);
+		VectorMA (end, u, up, end);
+
+		VectorSubtract (end, start, aim);
+		VectorNormalize (aim);
+	}
+
+	if (self->s.skinnum <= 1)
+	{
+		// RAFAEL 24-APR-98
+		// droped the damage from 15 to 5 
+		monster_fire_ionripper (self, start, aim, 5, 600, flash_index, EF_IONRIPPER);
+
+	}
+	else if (self->s.skinnum <= 3)
+	{
+		
+		monster_fire_blueblaster (self, start, aim, 1, 600, MZ_BLUEHYPERBLASTER, EF_BLUEHYPERBLASTER);
+		
+	}
+	else
+	{
+		if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME))
+			self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME;
+
+		soldierh_laserbeam (self, flash_index);
+
+		if (level.time >= self->monsterinfo.pausetime)
+			self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+		else
+			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+	}
+}
+
+// ATTACK1 (blaster/shotgun)
+
+void soldierh_hyper_refire1 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+	else if (self->s.skinnum < 4)
+	{
+		if (qrandom() < 0.7)
+			self->s.frame = FRAME_attak103;
+		else
+			gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+	}
+}
+
+void soldierh_ripper1 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		soldierh_fire (self, 0);
+	else if (self->s.skinnum < 4)
+		soldierh_fire (self, 0);
+}
+
+
+void soldierh_fire1 (edict_t *self)
+{
+	soldierh_fire (self, 0);
+}
+
+void soldierh_attack1_refire1 (edict_t *self)
+{
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+	else
+		self->monsterinfo.nextframe = FRAME_attak110;
+}
+
+void soldierh_attack1_refire2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak102;
+}
+
+void soldierh_hyper_sound (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+	else if (self->s.skinnum < 4)
+		gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbl1a.wav"), 1, ATTN_NORM, 0);
+	else
+		return;
+}
+
+mframe_t soldierh_frames_attack1 [] =
+{
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  soldierh_hyper_sound,
+	ai_charge, 0,  soldierh_fire1,
+	ai_charge, 0,  soldierh_ripper1,
+	ai_charge, 0,  soldierh_ripper1,
+	ai_charge, 0,  soldierh_attack1_refire1,
+	ai_charge, 0,  soldierh_hyper_refire1,
+	ai_charge, 0,  soldierh_cock,
+	ai_charge, 0,  soldierh_attack1_refire2,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL
+};
+mmove_t soldierh_move_attack1 = {FRAME_attak101, FRAME_attak112, soldierh_frames_attack1, soldierh_run};
+
+// ATTACK2 (blaster/shotgun)
+
+void soldierh_hyper_refire2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+	else if (self->s.skinnum < 4)
+	{
+		if (qrandom() < 0.7)
+			self->s.frame = FRAME_attak205;
+		else
+			gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+	}
+}
+
+void soldierh_ripper2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		soldierh_fire (self, 1);
+	else if (self->s.skinnum < 4)
+		soldierh_fire (self, 1);
+}
+
+void soldierh_fire2 (edict_t *self)
+{
+	soldierh_fire (self, 1);
+}
+
+
+void soldierh_attack2_refire1 (edict_t *self)
+{
+	if (self->s.skinnum > 1)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE))
+		self->monsterinfo.nextframe = FRAME_attak204;
+	else
+		self->monsterinfo.nextframe = FRAME_attak216;
+}
+
+void soldierh_attack2_refire2 (edict_t *self)
+{
+	if (self->s.skinnum < 2)
+		return;
+
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (qrandom() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) && self->s.skinnum < 4)
+		self->monsterinfo.nextframe = FRAME_attak204;
+}
+
+mframe_t soldierh_frames_attack2 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_hyper_sound,
+	ai_charge, 0, soldierh_fire2,
+	ai_charge, 0, soldierh_ripper2,
+	ai_charge, 0, soldierh_ripper2,
+	ai_charge, 0, soldierh_attack2_refire1,
+	ai_charge, 0, soldierh_hyper_refire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_cock,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_attack2_refire2,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldierh_move_attack2 = {FRAME_attak201, FRAME_attak218, soldierh_frames_attack2, soldierh_run};
+
+// ATTACK3 (duck and shoot)
+
+void soldierh_duck_down (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_DUCKED)
+		return;
+	self->monsterinfo.aiflags |= AI_DUCKED;
+	self->maxs[2] -= 32;
+	self->takedamage = DAMAGE_YES;
+	self->monsterinfo.pausetime = level.time + 1;
+	gi.linkentity (self);
+}
+
+void soldierh_duck_up (edict_t *self)
+{
+	self->monsterinfo.aiflags &= ~AI_DUCKED;
+	self->maxs[2] += 32;
+	self->takedamage = DAMAGE_AIM;
+	gi.linkentity (self);
+}
+
+void soldierh_fire3 (edict_t *self)
+{
+	soldierh_duck_down (self);
+	soldierh_fire (self, 2);
+}
+
+void soldierh_attack3_refire (edict_t *self)
+{
+	if ((level.time + 0.4) < self->monsterinfo.pausetime)
+		self->monsterinfo.nextframe = FRAME_attak303;
+}
+
+mframe_t soldierh_frames_attack3 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_fire3,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_attack3_refire,
+	ai_charge, 0, soldierh_duck_up,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldierh_move_attack3 = {FRAME_attak301, FRAME_attak309, soldierh_frames_attack3, soldierh_run};
+
+// ATTACK4 (machinegun)
+
+void soldierh_fire4 (edict_t *self)
+{
+	soldierh_fire (self, 3);
+//
+//	if (self->enemy->health <= 0)
+//		return;
+//
+//	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+//		self->monsterinfo.nextframe = FRAME_attak402;
+}
+
+mframe_t soldierh_frames_attack4 [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_fire4,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t soldierh_move_attack4 = {FRAME_attak401, FRAME_attak406, soldierh_frames_attack4, soldierh_run};
+
+/*
+// ATTACK5 (prone)
+
+void soldierh_fire5 (edict_t *self)
+{
+	soldierh_fire (self, 4);
+}
+
+void soldierh_attack5_refire (edict_t *self)
+{
+	if (self->enemy->health <= 0)
+		return;
+
+	if ( ((skill->value == 3) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE) )
+		self->monsterinfo.nextframe = FRAME_attak505;
+}
+
+mframe_t soldierh_frames_attack5 [] =
+{
+	ai_charge, 8, NULL,
+	ai_charge, 8, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_fire5,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, soldierh_attack5_refire
+};
+mmove_t soldierh_move_attack5 = {FRAME_attak501, FRAME_attak508, soldierh_frames_attack5, soldierh_run};
+*/
+
+// ATTACK6 (run & shoot)
+
+void soldierh_fire8 (edict_t *self)
+{
+	soldierh_fire (self, 7);
+}
+
+void soldierh_attack6_refire (edict_t *self)
+{
+	if (self->enemy->health <= 0)
+		return;
+
+	if (range(self, self->enemy) < RANGE_MID)
+		return;
+
+	if (skill->value == 3)
+		self->monsterinfo.nextframe = FRAME_runs03;
+}
+
+mframe_t soldierh_frames_attack6 [] =
+{
+	ai_charge, 10, NULL,
+	ai_charge,  4, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 11, soldierh_fire8,
+	ai_charge, 13, NULL,
+	ai_charge, 18, NULL,
+	ai_charge, 15, NULL,
+	ai_charge, 14, NULL,
+	ai_charge, 11, NULL,
+	ai_charge,  8, NULL,
+	ai_charge, 11, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 12, NULL,
+	ai_charge, 17, soldierh_attack6_refire
+};
+mmove_t soldierh_move_attack6 = {FRAME_runs01, FRAME_runs14, soldierh_frames_attack6, soldierh_run};
+
+void soldierh_attack(edict_t *self)
+{
+	if (self->s.skinnum < 4)
+	{
+		if (qrandom() < 0.5)
+			self->monsterinfo.currentmove = &soldierh_move_attack1;
+		else
+			self->monsterinfo.currentmove = &soldierh_move_attack2;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &soldierh_move_attack4;
+	}
+}
+
+
+//
+// SIGHT
+//
+
+void soldierh_sight(edict_t *self, edict_t *)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0);
+
+	if ((skill->value > 0) && (range(self, self->enemy) >= RANGE_MID))
+	{
+		if (qrandom() > 0.5)
+		{
+			if (self->s.skinnum < 4)	
+				self->monsterinfo.currentmove = &soldierh_move_attack6;
+			else
+				self->monsterinfo.currentmove = &soldierh_move_attack4;
+		}
+	}
+}
+
+//
+// DUCK
+//
+
+void soldierh_duck_hold (edict_t *self)
+{
+	if (level.time >= self->monsterinfo.pausetime)
+		self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
+	else
+		self->monsterinfo.aiflags |= AI_HOLD_FRAME;
+}
+
+mframe_t soldierh_frames_duck [] =
+{
+	ai_move, 5, soldierh_duck_down,
+	ai_move, -1, soldierh_duck_hold,
+	ai_move, 1,  NULL,
+	ai_move, 0,  soldierh_duck_up,
+	ai_move, 5,  NULL
+};
+mmove_t soldierh_move_duck = {FRAME_duck01, FRAME_duck05, soldierh_frames_duck, soldierh_run};
+
+void soldierh_dodge (edict_t *self, edict_t *attacker, float eta)
+{
+	float	r;
+
+	r = qrandom();
+	if (r > 0.25)
+		return;
+
+	if (!self->enemy)
+		self->enemy = attacker;
+
+	if (skill->value == 0)
+	{
+		self->monsterinfo.currentmove = &soldierh_move_duck;
+		return;
+	}
+
+	self->monsterinfo.pausetime = level.time + eta + 0.3;
+	r = qrandom();
+
+	if (skill->value == 1)
+	{
+		if (r > 0.33)
+			self->monsterinfo.currentmove = &soldierh_move_duck;
+		else
+			self->monsterinfo.currentmove = &soldierh_move_attack3;
+		return;
+	}
+
+	if (skill->value >= 2)
+	{
+		if (r > 0.66)
+			self->monsterinfo.currentmove = &soldierh_move_duck;
+		else
+			self->monsterinfo.currentmove = &soldierh_move_attack3;
+		return;
+	}
+
+	self->monsterinfo.currentmove = &soldierh_move_attack3;
+}
+
+
+//
+// DEATH
+//
+
+void soldierh_fire6 (edict_t *self)
+{
+	
+	// no fire laser
+	if (self->s.skinnum < 4)
+		soldierh_fire (self, 5);
+
+}
+
+void soldierh_fire7 (edict_t *self)
+{
+	
+	// no fire laser
+	if (self->s.skinnum < 4)
+		soldierh_fire (self, 6);
+
+}
+
+void soldierh_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, -8);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t soldierh_frames_death1 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldierh_fire6,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   soldierh_fire7,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_death1 = {FRAME_death101, FRAME_death136, soldierh_frames_death1, soldierh_dead};
+
+mframe_t soldierh_frames_death2 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_death2 = {FRAME_death201, FRAME_death235, soldierh_frames_death2, soldierh_dead};
+
+mframe_t soldierh_frames_death3 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+};
+mmove_t soldierh_move_death3 = {FRAME_death301, FRAME_death345, soldierh_frames_death3, soldierh_dead};
+
+mframe_t soldierh_frames_death4 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_death4 = {FRAME_death401, FRAME_death453, soldierh_frames_death4, soldierh_dead};
+
+mframe_t soldierh_frames_death5 [] =
+{
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_death5 = {FRAME_death501, FRAME_death524, soldierh_frames_death5, soldierh_dead};
+
+mframe_t soldierh_frames_death6 [] =
+{
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t soldierh_move_death6 = {FRAME_death601, FRAME_death610, soldierh_frames_death6, soldierh_dead};
+
+void soldierh_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t point)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 3; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC);
+		
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+	self->s.skinnum |= 1;
+
+	if (self->s.skinnum == 1)
+		gi.sound (self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0);
+	else if (self->s.skinnum == 3)
+		gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	else // (self->s.skinnum == 5)
+		gi.sound (self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0);
+
+	if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4)
+	{
+		// head shot
+		self->monsterinfo.currentmove = &soldierh_move_death3;
+		return;
+	}
+
+	n = rand() % 5;
+	if (n == 0)
+		self->monsterinfo.currentmove = &soldierh_move_death1;
+	else if (n == 1)
+		self->monsterinfo.currentmove = &soldierh_move_death2;
+	else if (n == 2)
+		self->monsterinfo.currentmove = &soldierh_move_death4;
+	else if (n == 3)
+		self->monsterinfo.currentmove = &soldierh_move_death5;
+	else
+		self->monsterinfo.currentmove = &soldierh_move_death6;
+}
+
+
+//
+// SPAWN
+//
+
+void SP_monster_soldier_h (edict_t *self)
+{
+
+	self->s.modelindex = gi.modelindex ("models/monsters/soldierh/tris.md2");
+	self->monsterinfo.scale = MODEL_SCALE;
+	VectorSet (self->mins, -16, -16, -24);
+	VectorSet (self->maxs, 16, 16, 32);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	sound_idle =	gi.soundindex ("soldier/solidle1.wav");
+	sound_sight1 =	gi.soundindex ("soldier/solsght1.wav");
+	sound_sight2 =	gi.soundindex ("soldier/solsrch1.wav");
+	sound_cock =	gi.soundindex ("infantry/infatck3.wav");
+
+	self->mass = 100;
+
+	self->pain = soldierh_pain;
+	self->die = soldierh_die;
+
+	self->monsterinfo.stand = soldierh_stand;
+	self->monsterinfo.walk = soldierh_walk;
+	self->monsterinfo.run = soldierh_run;
+	self->monsterinfo.dodge = soldierh_dodge;
+	self->monsterinfo.attack = soldierh_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = soldierh_sight;
+
+	gi.linkentity (self);
+
+	// self->monsterinfo.stand (self);
+	self->monsterinfo.currentmove = &soldierh_move_stand3;	
+
+	walkmonster_start (self);
+}
+
+
+/*QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier_ripper (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_h (self);
+
+	sound_pain_light = gi.soundindex ("soldier/solpain2.wav");
+	sound_death_light =	gi.soundindex ("soldier/soldeth2.wav");
+	
+	gi.modelindex ("models/objects/boomrang/tris.md2");
+	gi.soundindex ("misc/lasfly.wav");
+	gi.soundindex ("soldier/solatck2.wav");
+
+	self->s.skinnum = 0;
+	self->health = 50;
+	self->gib_health = -30;
+}
+
+/*QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier_hypergun (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_h (self);
+
+	gi.modelindex ("models/objects/blaser/tris.md2");
+	sound_pain = gi.soundindex ("soldier/solpain1.wav");
+	sound_death = gi.soundindex ("soldier/soldeth1.wav");
+	gi.soundindex ("soldier/solatck1.wav");
+
+	self->s.skinnum = 2;
+	self->health = 60;
+	self->gib_health = -30;
+}
+
+/*QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_soldier_lasergun (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	SP_monster_soldier_h (self);
+
+	sound_pain_ss = gi.soundindex ("soldier/solpain3.wav");
+	sound_death_ss = gi.soundindex ("soldier/soldeth3.wav");
+	gi.soundindex ("soldier/solatck3.wav");
+
+	self->s.skinnum = 4;
+	self->health = 70;
+	self->gib_health = -30;
+		
+}
+
+// END 13-APR-98
--- /dev/null
+++ b/xatrix/m_soldier.h
@@ -1,0 +1,481 @@
+// G:\quake2\baseq2\models/monsters/soldier
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak101        	0
+#define FRAME_attak102        	1
+#define FRAME_attak103        	2
+#define FRAME_attak104        	3
+#define FRAME_attak105        	4
+#define FRAME_attak106        	5
+#define FRAME_attak107        	6
+#define FRAME_attak108        	7
+#define FRAME_attak109        	8
+#define FRAME_attak110        	9
+#define FRAME_attak111        	10
+#define FRAME_attak112        	11
+#define FRAME_attak201        	12
+#define FRAME_attak202        	13
+#define FRAME_attak203        	14
+#define FRAME_attak204        	15
+#define FRAME_attak205        	16
+#define FRAME_attak206        	17
+#define FRAME_attak207        	18
+#define FRAME_attak208        	19
+#define FRAME_attak209        	20
+#define FRAME_attak210        	21
+#define FRAME_attak211        	22
+#define FRAME_attak212        	23
+#define FRAME_attak213        	24
+#define FRAME_attak214        	25
+#define FRAME_attak215        	26
+#define FRAME_attak216        	27
+#define FRAME_attak217        	28
+#define FRAME_attak218        	29
+#define FRAME_attak301        	30
+#define FRAME_attak302        	31
+#define FRAME_attak303        	32
+#define FRAME_attak304        	33
+#define FRAME_attak305        	34
+#define FRAME_attak306        	35
+#define FRAME_attak307        	36
+#define FRAME_attak308        	37
+#define FRAME_attak309        	38
+#define FRAME_attak401        	39
+#define FRAME_attak402        	40
+#define FRAME_attak403        	41
+#define FRAME_attak404        	42
+#define FRAME_attak405        	43
+#define FRAME_attak406        	44
+#define FRAME_duck01          	45
+#define FRAME_duck02          	46
+#define FRAME_duck03          	47
+#define FRAME_duck04          	48
+#define FRAME_duck05          	49
+#define FRAME_pain101         	50
+#define FRAME_pain102         	51
+#define FRAME_pain103         	52
+#define FRAME_pain104         	53
+#define FRAME_pain105         	54
+#define FRAME_pain201         	55
+#define FRAME_pain202         	56
+#define FRAME_pain203         	57
+#define FRAME_pain204         	58
+#define FRAME_pain205         	59
+#define FRAME_pain206         	60
+#define FRAME_pain207         	61
+#define FRAME_pain301         	62
+#define FRAME_pain302         	63
+#define FRAME_pain303         	64
+#define FRAME_pain304         	65
+#define FRAME_pain305         	66
+#define FRAME_pain306         	67
+#define FRAME_pain307         	68
+#define FRAME_pain308         	69
+#define FRAME_pain309         	70
+#define FRAME_pain310         	71
+#define FRAME_pain311         	72
+#define FRAME_pain312         	73
+#define FRAME_pain313         	74
+#define FRAME_pain314         	75
+#define FRAME_pain315         	76
+#define FRAME_pain316         	77
+#define FRAME_pain317         	78
+#define FRAME_pain318         	79
+#define FRAME_pain401         	80
+#define FRAME_pain402         	81
+#define FRAME_pain403         	82
+#define FRAME_pain404         	83
+#define FRAME_pain405         	84
+#define FRAME_pain406         	85
+#define FRAME_pain407         	86
+#define FRAME_pain408         	87
+#define FRAME_pain409         	88
+#define FRAME_pain410         	89
+#define FRAME_pain411         	90
+#define FRAME_pain412         	91
+#define FRAME_pain413         	92
+#define FRAME_pain414         	93
+#define FRAME_pain415         	94
+#define FRAME_pain416         	95
+#define FRAME_pain417         	96
+#define FRAME_run01           	97
+#define FRAME_run02           	98
+#define FRAME_run03           	99
+#define FRAME_run04           	100
+#define FRAME_run05           	101
+#define FRAME_run06           	102
+#define FRAME_run07           	103
+#define FRAME_run08           	104
+#define FRAME_run09           	105
+#define FRAME_run10           	106
+#define FRAME_run11           	107
+#define FRAME_run12           	108
+#define FRAME_runs01          	109
+#define FRAME_runs02          	110
+#define FRAME_runs03          	111
+#define FRAME_runs04          	112
+#define FRAME_runs05          	113
+#define FRAME_runs06          	114
+#define FRAME_runs07          	115
+#define FRAME_runs08          	116
+#define FRAME_runs09          	117
+#define FRAME_runs10          	118
+#define FRAME_runs11          	119
+#define FRAME_runs12          	120
+#define FRAME_runs13          	121
+#define FRAME_runs14          	122
+#define FRAME_runs15          	123
+#define FRAME_runs16          	124
+#define FRAME_runs17          	125
+#define FRAME_runs18          	126
+#define FRAME_runt01          	127
+#define FRAME_runt02          	128
+#define FRAME_runt03          	129
+#define FRAME_runt04          	130
+#define FRAME_runt05          	131
+#define FRAME_runt06          	132
+#define FRAME_runt07          	133
+#define FRAME_runt08          	134
+#define FRAME_runt09          	135
+#define FRAME_runt10          	136
+#define FRAME_runt11          	137
+#define FRAME_runt12          	138
+#define FRAME_runt13          	139
+#define FRAME_runt14          	140
+#define FRAME_runt15          	141
+#define FRAME_runt16          	142
+#define FRAME_runt17          	143
+#define FRAME_runt18          	144
+#define FRAME_runt19          	145
+#define FRAME_stand101        	146
+#define FRAME_stand102        	147
+#define FRAME_stand103        	148
+#define FRAME_stand104        	149
+#define FRAME_stand105        	150
+#define FRAME_stand106        	151
+#define FRAME_stand107        	152
+#define FRAME_stand108        	153
+#define FRAME_stand109        	154
+#define FRAME_stand110        	155
+#define FRAME_stand111        	156
+#define FRAME_stand112        	157
+#define FRAME_stand113        	158
+#define FRAME_stand114        	159
+#define FRAME_stand115        	160
+#define FRAME_stand116        	161
+#define FRAME_stand117        	162
+#define FRAME_stand118        	163
+#define FRAME_stand119        	164
+#define FRAME_stand120        	165
+#define FRAME_stand121        	166
+#define FRAME_stand122        	167
+#define FRAME_stand123        	168
+#define FRAME_stand124        	169
+#define FRAME_stand125        	170
+#define FRAME_stand126        	171
+#define FRAME_stand127        	172
+#define FRAME_stand128        	173
+#define FRAME_stand129        	174
+#define FRAME_stand130        	175
+#define FRAME_stand301        	176
+#define FRAME_stand302        	177
+#define FRAME_stand303        	178
+#define FRAME_stand304        	179
+#define FRAME_stand305        	180
+#define FRAME_stand306        	181
+#define FRAME_stand307        	182
+#define FRAME_stand308        	183
+#define FRAME_stand309        	184
+#define FRAME_stand310        	185
+#define FRAME_stand311        	186
+#define FRAME_stand312        	187
+#define FRAME_stand313        	188
+#define FRAME_stand314        	189
+#define FRAME_stand315        	190
+#define FRAME_stand316        	191
+#define FRAME_stand317        	192
+#define FRAME_stand318        	193
+#define FRAME_stand319        	194
+#define FRAME_stand320        	195
+#define FRAME_stand321        	196
+#define FRAME_stand322        	197
+#define FRAME_stand323        	198
+#define FRAME_stand324        	199
+#define FRAME_stand325        	200
+#define FRAME_stand326        	201
+#define FRAME_stand327        	202
+#define FRAME_stand328        	203
+#define FRAME_stand329        	204
+#define FRAME_stand330        	205
+#define FRAME_stand331        	206
+#define FRAME_stand332        	207
+#define FRAME_stand333        	208
+#define FRAME_stand334        	209
+#define FRAME_stand335        	210
+#define FRAME_stand336        	211
+#define FRAME_stand337        	212
+#define FRAME_stand338        	213
+#define FRAME_stand339        	214
+#define FRAME_walk101         	215
+#define FRAME_walk102         	216
+#define FRAME_walk103         	217
+#define FRAME_walk104         	218
+#define FRAME_walk105         	219
+#define FRAME_walk106         	220
+#define FRAME_walk107         	221
+#define FRAME_walk108         	222
+#define FRAME_walk109         	223
+#define FRAME_walk110         	224
+#define FRAME_walk111         	225
+#define FRAME_walk112         	226
+#define FRAME_walk113         	227
+#define FRAME_walk114         	228
+#define FRAME_walk115         	229
+#define FRAME_walk116         	230
+#define FRAME_walk117         	231
+#define FRAME_walk118         	232
+#define FRAME_walk119         	233
+#define FRAME_walk120         	234
+#define FRAME_walk121         	235
+#define FRAME_walk122         	236
+#define FRAME_walk123         	237
+#define FRAME_walk124         	238
+#define FRAME_walk125         	239
+#define FRAME_walk126         	240
+#define FRAME_walk127         	241
+#define FRAME_walk128         	242
+#define FRAME_walk129         	243
+#define FRAME_walk130         	244
+#define FRAME_walk131         	245
+#define FRAME_walk132         	246
+#define FRAME_walk133         	247
+#define FRAME_walk201         	248
+#define FRAME_walk202         	249
+#define FRAME_walk203         	250
+#define FRAME_walk204         	251
+#define FRAME_walk205         	252
+#define FRAME_walk206         	253
+#define FRAME_walk207         	254
+#define FRAME_walk208         	255
+#define FRAME_walk209         	256
+#define FRAME_walk210         	257
+#define FRAME_walk211         	258
+#define FRAME_walk212         	259
+#define FRAME_walk213         	260
+#define FRAME_walk214         	261
+#define FRAME_walk215         	262
+#define FRAME_walk216         	263
+#define FRAME_walk217         	264
+#define FRAME_walk218         	265
+#define FRAME_walk219         	266
+#define FRAME_walk220         	267
+#define FRAME_walk221         	268
+#define FRAME_walk222         	269
+#define FRAME_walk223         	270
+#define FRAME_walk224         	271
+#define FRAME_death101        	272
+#define FRAME_death102        	273
+#define FRAME_death103        	274
+#define FRAME_death104        	275
+#define FRAME_death105        	276
+#define FRAME_death106        	277
+#define FRAME_death107        	278
+#define FRAME_death108        	279
+#define FRAME_death109        	280
+#define FRAME_death110        	281
+#define FRAME_death111        	282
+#define FRAME_death112        	283
+#define FRAME_death113        	284
+#define FRAME_death114        	285
+#define FRAME_death115        	286
+#define FRAME_death116        	287
+#define FRAME_death117        	288
+#define FRAME_death118        	289
+#define FRAME_death119        	290
+#define FRAME_death120        	291
+#define FRAME_death121        	292
+#define FRAME_death122        	293
+#define FRAME_death123        	294
+#define FRAME_death124        	295
+#define FRAME_death125        	296
+#define FRAME_death126        	297
+#define FRAME_death127        	298
+#define FRAME_death128        	299
+#define FRAME_death129        	300
+#define FRAME_death130        	301
+#define FRAME_death131        	302
+#define FRAME_death132        	303
+#define FRAME_death133        	304
+#define FRAME_death134        	305
+#define FRAME_death135        	306
+#define FRAME_death136        	307
+#define FRAME_death201        	308
+#define FRAME_death202        	309
+#define FRAME_death203        	310
+#define FRAME_death204        	311
+#define FRAME_death205        	312
+#define FRAME_death206        	313
+#define FRAME_death207        	314
+#define FRAME_death208        	315
+#define FRAME_death209        	316
+#define FRAME_death210        	317
+#define FRAME_death211        	318
+#define FRAME_death212        	319
+#define FRAME_death213        	320
+#define FRAME_death214        	321
+#define FRAME_death215        	322
+#define FRAME_death216        	323
+#define FRAME_death217        	324
+#define FRAME_death218        	325
+#define FRAME_death219        	326
+#define FRAME_death220        	327
+#define FRAME_death221        	328
+#define FRAME_death222        	329
+#define FRAME_death223        	330
+#define FRAME_death224        	331
+#define FRAME_death225        	332
+#define FRAME_death226        	333
+#define FRAME_death227        	334
+#define FRAME_death228        	335
+#define FRAME_death229        	336
+#define FRAME_death230        	337
+#define FRAME_death231        	338
+#define FRAME_death232        	339
+#define FRAME_death233        	340
+#define FRAME_death234        	341
+#define FRAME_death235        	342
+#define FRAME_death301        	343
+#define FRAME_death302        	344
+#define FRAME_death303        	345
+#define FRAME_death304        	346
+#define FRAME_death305        	347
+#define FRAME_death306        	348
+#define FRAME_death307        	349
+#define FRAME_death308        	350
+#define FRAME_death309        	351
+#define FRAME_death310        	352
+#define FRAME_death311        	353
+#define FRAME_death312        	354
+#define FRAME_death313        	355
+#define FRAME_death314        	356
+#define FRAME_death315        	357
+#define FRAME_death316        	358
+#define FRAME_death317        	359
+#define FRAME_death318        	360
+#define FRAME_death319        	361
+#define FRAME_death320        	362
+#define FRAME_death321        	363
+#define FRAME_death322        	364
+#define FRAME_death323        	365
+#define FRAME_death324        	366
+#define FRAME_death325        	367
+#define FRAME_death326        	368
+#define FRAME_death327        	369
+#define FRAME_death328        	370
+#define FRAME_death329        	371
+#define FRAME_death330        	372
+#define FRAME_death331        	373
+#define FRAME_death332        	374
+#define FRAME_death333        	375
+#define FRAME_death334        	376
+#define FRAME_death335        	377
+#define FRAME_death336        	378
+#define FRAME_death337        	379
+#define FRAME_death338        	380
+#define FRAME_death339        	381
+#define FRAME_death340        	382
+#define FRAME_death341        	383
+#define FRAME_death342        	384
+#define FRAME_death343        	385
+#define FRAME_death344        	386
+#define FRAME_death345        	387
+#define FRAME_death401        	388
+#define FRAME_death402        	389
+#define FRAME_death403        	390
+#define FRAME_death404        	391
+#define FRAME_death405        	392
+#define FRAME_death406        	393
+#define FRAME_death407        	394
+#define FRAME_death408        	395
+#define FRAME_death409        	396
+#define FRAME_death410        	397
+#define FRAME_death411        	398
+#define FRAME_death412        	399
+#define FRAME_death413        	400
+#define FRAME_death414        	401
+#define FRAME_death415        	402
+#define FRAME_death416        	403
+#define FRAME_death417        	404
+#define FRAME_death418        	405
+#define FRAME_death419        	406
+#define FRAME_death420        	407
+#define FRAME_death421        	408
+#define FRAME_death422        	409
+#define FRAME_death423        	410
+#define FRAME_death424        	411
+#define FRAME_death425        	412
+#define FRAME_death426        	413
+#define FRAME_death427        	414
+#define FRAME_death428        	415
+#define FRAME_death429        	416
+#define FRAME_death430        	417
+#define FRAME_death431        	418
+#define FRAME_death432        	419
+#define FRAME_death433        	420
+#define FRAME_death434        	421
+#define FRAME_death435        	422
+#define FRAME_death436        	423
+#define FRAME_death437        	424
+#define FRAME_death438        	425
+#define FRAME_death439        	426
+#define FRAME_death440        	427
+#define FRAME_death441        	428
+#define FRAME_death442        	429
+#define FRAME_death443        	430
+#define FRAME_death444        	431
+#define FRAME_death445        	432
+#define FRAME_death446        	433
+#define FRAME_death447        	434
+#define FRAME_death448        	435
+#define FRAME_death449        	436
+#define FRAME_death450        	437
+#define FRAME_death451        	438
+#define FRAME_death452        	439
+#define FRAME_death453        	440
+#define FRAME_death501        	441
+#define FRAME_death502        	442
+#define FRAME_death503        	443
+#define FRAME_death504        	444
+#define FRAME_death505        	445
+#define FRAME_death506        	446
+#define FRAME_death507        	447
+#define FRAME_death508        	448
+#define FRAME_death509        	449
+#define FRAME_death510        	450
+#define FRAME_death511        	451
+#define FRAME_death512        	452
+#define FRAME_death513        	453
+#define FRAME_death514        	454
+#define FRAME_death515        	455
+#define FRAME_death516        	456
+#define FRAME_death517        	457
+#define FRAME_death518        	458
+#define FRAME_death519        	459
+#define FRAME_death520        	460
+#define FRAME_death521        	461
+#define FRAME_death522        	462
+#define FRAME_death523        	463
+#define FRAME_death524        	464
+#define FRAME_death601        	465
+#define FRAME_death602        	466
+#define FRAME_death603        	467
+#define FRAME_death604        	468
+#define FRAME_death605        	469
+#define FRAME_death606        	470
+#define FRAME_death607        	471
+#define FRAME_death608        	472
+#define FRAME_death609        	473
+#define FRAME_death610        	474
+
+#define MODEL_SCALE		1.200000
--- /dev/null
+++ b/xatrix/m_supertank.c
@@ -1,0 +1,701 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_supertank.h"
+
+qboolean visible (edict_t *self, edict_t *other);
+
+static int	sound_pain1;
+static int	sound_pain2;
+static int	sound_pain3;
+static int	sound_death;
+static int	sound_search1;
+static int	sound_search2;
+
+static	int	tread_sound;
+
+void BossExplode (edict_t *self);
+
+void TreadSound (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, tread_sound, 1, ATTN_NORM, 0);
+}
+
+void supertank_search (edict_t *self)
+{
+	if (qrandom() < 0.5)
+		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
+	else
+		gi.sound (self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
+}
+
+
+void supertank_dead (edict_t *self);
+void supertankRocket (edict_t *self);
+void supertankMachineGun (edict_t *self);
+void supertank_reattack1(edict_t *self);
+
+
+//
+// stand
+//
+
+mframe_t supertank_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	supertank_move_stand = {FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, NULL};
+	
+void supertank_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &supertank_move_stand;
+}
+
+
+mframe_t supertank_frames_run [] =
+{
+	ai_run, 12,	TreadSound,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL,
+	ai_run, 12,	NULL
+};
+mmove_t	supertank_move_run = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, NULL};
+
+//
+// walk
+//
+
+
+mframe_t supertank_frames_forward [] =
+{
+	ai_walk, 4,	TreadSound,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	NULL
+};
+mmove_t	supertank_move_forward = {FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, NULL};
+
+void supertank_forward (edict_t *self)
+{
+		self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void supertank_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &supertank_move_forward;
+}
+
+void supertank_run (edict_t *self)
+{
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+		self->monsterinfo.currentmove = &supertank_move_stand;
+	else
+		self->monsterinfo.currentmove = &supertank_move_run;
+}
+
+mframe_t supertank_frames_turn_right [] =
+{
+	ai_move,	0,	TreadSound,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_turn_right = {FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run};
+
+mframe_t supertank_frames_turn_left [] =
+{
+	ai_move,	0,	TreadSound,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_turn_left = {FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run};
+
+
+mframe_t supertank_frames_pain3 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain3 = {FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run};
+
+mframe_t supertank_frames_pain2 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain2 = {FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run};
+
+mframe_t supertank_frames_pain1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_pain1 = {FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run};
+
+mframe_t supertank_frames_death1 [] =
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	BossExplode
+};
+mmove_t supertank_move_death = {FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead};
+
+mframe_t supertank_frames_backward[] =
+{
+	ai_walk, 0,	TreadSound,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL,
+	ai_walk, 0,	NULL
+};
+mmove_t	supertank_move_backward = {FRAME_backwd_1, FRAME_backwd_18, supertank_frames_backward, NULL};
+
+mframe_t supertank_frames_attack4[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack4 = {FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run};
+
+mframe_t supertank_frames_attack3[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack3 = {FRAME_attak3_1, FRAME_attak3_27, supertank_frames_attack3, supertank_run};
+
+mframe_t supertank_frames_attack2[]=
+{
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	NULL,
+	ai_charge,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	supertankRocket,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_attack2 = {FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run};
+
+mframe_t supertank_frames_attack1[]=
+{
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+	ai_charge,	0,	supertankMachineGun,
+
+};
+mmove_t supertank_move_attack1 = {FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1};
+
+mframe_t supertank_frames_end_attack1[]=
+{
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL,
+	ai_move,	0,	NULL
+};
+mmove_t supertank_move_end_attack1 = {FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run};
+
+
+void supertank_reattack1(edict_t *self)
+{
+	if (visible(self, self->enemy))
+		if (qrandom() < 0.9)
+			self->monsterinfo.currentmove = &supertank_move_attack1;
+		else
+			self->monsterinfo.currentmove = &supertank_move_end_attack1;	
+	else
+		self->monsterinfo.currentmove = &supertank_move_end_attack1;
+}
+
+void supertank_pain (edict_t *self, edict_t *, float, int damage)
+{
+
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum = 1;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	// Lessen the chance of him going into his pain frames
+	if (damage <=25)
+		if (qrandom()<0.2)
+			return;
+
+	// Don't go into pain if he's firing his rockets
+	if (skill->value >= 2)
+		if ( (self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14) )
+			return;
+
+	self->pain_debounce_time = level.time + 3;
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 10)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain1;
+	}
+	else if (damage <= 25)
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain2;
+	}
+	else
+	{
+		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM,0);
+		self->monsterinfo.currentmove = &supertank_move_pain3;
+	}
+};
+
+
+void supertankRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak2_8)
+		flash_number = MZ2_SUPERTANK_ROCKET_1;
+	else if (self->s.frame == FRAME_attak2_11)
+		flash_number = MZ2_SUPERTANK_ROCKET_2;
+	else // (self->s.frame == FRAME_attak2_14)
+		flash_number = MZ2_SUPERTANK_ROCKET_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_rocket (self, start, dir, 50, 500, flash_number);
+}	
+
+void supertankMachineGun (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	flash_number = MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1);
+
+	//FIXME!!!
+	dir[0] = 0;
+	dir[1] = self->s.angles[1];
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		VectorMA (vec, 0, self->enemy->velocity, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, forward);
+		VectorNormalize (forward);
+  }
+
+	monster_fire_bullet (self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}	
+
+
+void supertank_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+	//float	r;
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+
+	//r = random();
+
+	// Attack 1 == Chaingun
+	// Attack 2 == Rocket Launcher
+
+	if (range <= 160)
+	{
+		self->monsterinfo.currentmove = &supertank_move_attack1;
+	}
+	else
+	{	// fire rockets more often at distance
+		if (qrandom() < 0.3)
+			self->monsterinfo.currentmove = &supertank_move_attack1;
+		else
+			self->monsterinfo.currentmove = &supertank_move_attack2;
+	}
+}
+
+
+//
+// death
+//
+
+void supertank_dead (edict_t *self)
+{
+	VectorSet (self->mins, -60, -60, 0);
+	VectorSet (self->maxs, 60, 60, 72);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+
+void BossExplode (edict_t *self)
+{
+	vec3_t	org;
+	int		n;
+
+	self->think = BossExplode;
+	VectorCopy (self->s.origin, org);
+	org[2] += 24 + (rand()&15);
+	switch (self->count++)
+	{
+	case 0:
+		org[0] -= 24;
+		org[1] -= 24;
+		break;
+	case 1:
+		org[0] += 24;
+		org[1] += 24;
+		break;
+	case 2:
+		org[0] += 24;
+		org[1] -= 24;
+		break;
+	case 3:
+		org[0] -= 24;
+		org[1] += 24;
+		break;
+	case 4:
+		org[0] -= 48;
+		org[1] -= 48;
+		break;
+	case 5:
+		org[0] += 48;
+		org[1] += 48;
+		break;
+	case 6:
+		org[0] -= 48;
+		org[1] += 48;
+		break;
+	case 7:
+		org[0] += 48;
+		org[1] -= 48;
+		break;
+	case 8:
+		self->s.sound = 0;
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC);
+		for (n= 0; n < 8; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	gi.WriteByte (svc_temp_entity);
+	gi.WriteByte (TE_EXPLOSION1);
+	gi.WritePosition (org);
+	gi.multicast (self->s.origin, MULTICAST_PVS);
+
+	self->nextthink = level.time + 0.1;
+}
+
+
+void supertank_die (edict_t *self, edict_t *, edict_t *, int, vec3_t)
+{
+	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_NO;
+	self->count = 0;
+	self->monsterinfo.currentmove = &supertank_move_death;
+}
+
+//
+// monster_supertank
+//
+
+// RAFAEL (Powershield)
+
+/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield
+*/
+void SP_monster_supertank (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	sound_pain1 = gi.soundindex ("bosstank/btkpain1.wav");
+	sound_pain2 = gi.soundindex ("bosstank/btkpain2.wav");
+	sound_pain3 = gi.soundindex ("bosstank/btkpain3.wav");
+	sound_death = gi.soundindex ("bosstank/btkdeth1.wav");
+	sound_search1 = gi.soundindex ("bosstank/btkunqv1.wav");
+	sound_search2 = gi.soundindex ("bosstank/btkunqv2.wav");
+
+//	self->s.sound = gi.soundindex ("bosstank/btkengn1.wav");
+	tread_sound = gi.soundindex ("bosstank/btkengn1.wav");
+
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+	self->s.modelindex = gi.modelindex ("models/monsters/boss1/tris.md2");
+	VectorSet (self->mins, -64, -64, 0);
+	VectorSet (self->maxs, 64, 64, 112);
+
+	self->health = 1500;
+	self->gib_health = -500;
+	self->mass = 800;
+
+	self->pain = supertank_pain;
+	self->die = supertank_die;
+	self->monsterinfo.stand = supertank_stand;
+	self->monsterinfo.walk = supertank_walk;
+	self->monsterinfo.run = supertank_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = supertank_attack;
+	self->monsterinfo.search = supertank_search;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = NULL;
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &supertank_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	if (self->spawnflags & 8)
+	{
+		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
+		self->monsterinfo.power_armor_power = 400;
+	}
+	walkmonster_start(self);
+}
--- /dev/null
+++ b/xatrix/m_supertank.h
@@ -1,0 +1,260 @@
+// G:\quake2\baseq2\models/monsters/boss1/backup
+
+// This file generated by ModelGen - Do NOT Modify
+
+#define FRAME_attak1_1        	0
+#define FRAME_attak1_2        	1
+#define FRAME_attak1_3        	2
+#define FRAME_attak1_4        	3
+#define FRAME_attak1_5        	4
+#define FRAME_attak1_6        	5
+#define FRAME_attak1_7        	6
+#define FRAME_attak1_8        	7
+#define FRAME_attak1_9        	8
+#define FRAME_attak1_10       	9
+#define FRAME_attak1_11       	10
+#define FRAME_attak1_12       	11
+#define FRAME_attak1_13       	12
+#define FRAME_attak1_14       	13
+#define FRAME_attak1_15       	14
+#define FRAME_attak1_16       	15
+#define FRAME_attak1_17       	16
+#define FRAME_attak1_18       	17
+#define FRAME_attak1_19       	18
+#define FRAME_attak1_20       	19
+#define FRAME_attak2_1        	20
+#define FRAME_attak2_2        	21
+#define FRAME_attak2_3        	22
+#define FRAME_attak2_4        	23
+#define FRAME_attak2_5        	24
+#define FRAME_attak2_6        	25
+#define FRAME_attak2_7        	26
+#define FRAME_attak2_8        	27
+#define FRAME_attak2_9        	28
+#define FRAME_attak2_10       	29
+#define FRAME_attak2_11       	30
+#define FRAME_attak2_12       	31
+#define FRAME_attak2_13       	32
+#define FRAME_attak2_14       	33
+#define FRAME_attak2_15       	34
+#define FRAME_attak2_16       	35
+#define FRAME_attak2_17       	36
+#define FRAME_attak2_18       	37
+#define FRAME_attak2_19       	38
+#define FRAME_attak2_20       	39
+#define FRAME_attak2_21       	40
+#define FRAME_attak2_22       	41
+#define FRAME_attak2_23       	42
+#define FRAME_attak2_24       	43
+#define FRAME_attak2_25       	44
+#define FRAME_attak2_26       	45
+#define FRAME_attak2_27       	46
+#define FRAME_attak3_1        	47
+#define FRAME_attak3_2        	48
+#define FRAME_attak3_3        	49
+#define FRAME_attak3_4        	50
+#define FRAME_attak3_5        	51
+#define FRAME_attak3_6        	52
+#define FRAME_attak3_7        	53
+#define FRAME_attak3_8        	54
+#define FRAME_attak3_9        	55
+#define FRAME_attak3_10       	56
+#define FRAME_attak3_11       	57
+#define FRAME_attak3_12       	58
+#define FRAME_attak3_13       	59
+#define FRAME_attak3_14       	60
+#define FRAME_attak3_15       	61
+#define FRAME_attak3_16       	62
+#define FRAME_attak3_17       	63
+#define FRAME_attak3_18       	64
+#define FRAME_attak3_19       	65
+#define FRAME_attak3_20       	66
+#define FRAME_attak3_21       	67
+#define FRAME_attak3_22       	68
+#define FRAME_attak3_23       	69
+#define FRAME_attak3_24       	70
+#define FRAME_attak3_25       	71
+#define FRAME_attak3_26       	72
+#define FRAME_attak3_27       	73
+#define FRAME_attak4_1        	74
+#define FRAME_attak4_2        	75
+#define FRAME_attak4_3        	76
+#define FRAME_attak4_4        	77
+#define FRAME_attak4_5        	78
+#define FRAME_attak4_6        	79
+#define FRAME_backwd_1        	80
+#define FRAME_backwd_2        	81
+#define FRAME_backwd_3        	82
+#define FRAME_backwd_4        	83
+#define FRAME_backwd_5        	84
+#define FRAME_backwd_6        	85
+#define FRAME_backwd_7        	86
+#define FRAME_backwd_8        	87
+#define FRAME_backwd_9        	88
+#define FRAME_backwd_10       	89
+#define FRAME_backwd_11       	90
+#define FRAME_backwd_12       	91
+#define FRAME_backwd_13       	92
+#define FRAME_backwd_14       	93
+#define FRAME_backwd_15       	94
+#define FRAME_backwd_16       	95
+#define FRAME_backwd_17       	96
+#define FRAME_backwd_18       	97
+#define FRAME_death_1         	98
+#define FRAME_death_2         	99
+#define FRAME_death_3         	100
+#define FRAME_death_4         	101
+#define FRAME_death_5         	102
+#define FRAME_death_6         	103
+#define FRAME_death_7         	104
+#define FRAME_death_8         	105
+#define FRAME_death_9         	106
+#define FRAME_death_10        	107
+#define FRAME_death_11        	108
+#define FRAME_death_12        	109
+#define FRAME_death_13        	110
+#define FRAME_death_14        	111
+#define FRAME_death_15        	112
+#define FRAME_death_16        	113
+#define FRAME_death_17        	114
+#define FRAME_death_18        	115
+#define FRAME_death_19        	116
+#define FRAME_death_20        	117
+#define FRAME_death_21        	118
+#define FRAME_death_22        	119
+#define FRAME_death_23        	120
+#define FRAME_death_24        	121
+#define FRAME_death_31        	122
+#define FRAME_death_32        	123
+#define FRAME_death_33        	124
+#define FRAME_death_45        	125
+#define FRAME_death_46        	126
+#define FRAME_death_47        	127
+#define FRAME_forwrd_1        	128
+#define FRAME_forwrd_2        	129
+#define FRAME_forwrd_3        	130
+#define FRAME_forwrd_4        	131
+#define FRAME_forwrd_5        	132
+#define FRAME_forwrd_6        	133
+#define FRAME_forwrd_7        	134
+#define FRAME_forwrd_8        	135
+#define FRAME_forwrd_9        	136
+#define FRAME_forwrd_10       	137
+#define FRAME_forwrd_11       	138
+#define FRAME_forwrd_12       	139
+#define FRAME_forwrd_13       	140
+#define FRAME_forwrd_14       	141
+#define FRAME_forwrd_15       	142
+#define FRAME_forwrd_16       	143
+#define FRAME_forwrd_17       	144
+#define FRAME_forwrd_18       	145
+#define FRAME_left_1          	146
+#define FRAME_left_2          	147
+#define FRAME_left_3          	148
+#define FRAME_left_4          	149
+#define FRAME_left_5          	150
+#define FRAME_left_6          	151
+#define FRAME_left_7          	152
+#define FRAME_left_8          	153
+#define FRAME_left_9          	154
+#define FRAME_left_10         	155
+#define FRAME_left_11         	156
+#define FRAME_left_12         	157
+#define FRAME_left_13         	158
+#define FRAME_left_14         	159
+#define FRAME_left_15         	160
+#define FRAME_left_16         	161
+#define FRAME_left_17         	162
+#define FRAME_left_18         	163
+#define FRAME_pain1_1         	164
+#define FRAME_pain1_2         	165
+#define FRAME_pain1_3         	166
+#define FRAME_pain1_4         	167
+#define FRAME_pain2_5         	168
+#define FRAME_pain2_6         	169
+#define FRAME_pain2_7         	170
+#define FRAME_pain2_8         	171
+#define FRAME_pain3_9         	172
+#define FRAME_pain3_10        	173
+#define FRAME_pain3_11        	174
+#define FRAME_pain3_12        	175
+#define FRAME_right_1         	176
+#define FRAME_right_2         	177
+#define FRAME_right_3         	178
+#define FRAME_right_4         	179
+#define FRAME_right_5         	180
+#define FRAME_right_6         	181
+#define FRAME_right_7         	182
+#define FRAME_right_8         	183
+#define FRAME_right_9         	184
+#define FRAME_right_10        	185
+#define FRAME_right_11        	186
+#define FRAME_right_12        	187
+#define FRAME_right_13        	188
+#define FRAME_right_14        	189
+#define FRAME_right_15        	190
+#define FRAME_right_16        	191
+#define FRAME_right_17        	192
+#define FRAME_right_18        	193
+#define FRAME_stand_1         	194
+#define FRAME_stand_2         	195
+#define FRAME_stand_3         	196
+#define FRAME_stand_4         	197
+#define FRAME_stand_5         	198
+#define FRAME_stand_6         	199
+#define FRAME_stand_7         	200
+#define FRAME_stand_8         	201
+#define FRAME_stand_9         	202
+#define FRAME_stand_10        	203
+#define FRAME_stand_11        	204
+#define FRAME_stand_12        	205
+#define FRAME_stand_13        	206
+#define FRAME_stand_14        	207
+#define FRAME_stand_15        	208
+#define FRAME_stand_16        	209
+#define FRAME_stand_17        	210
+#define FRAME_stand_18        	211
+#define FRAME_stand_19        	212
+#define FRAME_stand_20        	213
+#define FRAME_stand_21        	214
+#define FRAME_stand_22        	215
+#define FRAME_stand_23        	216
+#define FRAME_stand_24        	217
+#define FRAME_stand_25        	218
+#define FRAME_stand_26        	219
+#define FRAME_stand_27        	220
+#define FRAME_stand_28        	221
+#define FRAME_stand_29        	222
+#define FRAME_stand_30        	223
+#define FRAME_stand_31        	224
+#define FRAME_stand_32        	225
+#define FRAME_stand_33        	226
+#define FRAME_stand_34        	227
+#define FRAME_stand_35        	228
+#define FRAME_stand_36        	229
+#define FRAME_stand_37        	230
+#define FRAME_stand_38        	231
+#define FRAME_stand_39        	232
+#define FRAME_stand_40        	233
+#define FRAME_stand_41        	234
+#define FRAME_stand_42        	235
+#define FRAME_stand_43        	236
+#define FRAME_stand_44        	237
+#define FRAME_stand_45        	238
+#define FRAME_stand_46        	239
+#define FRAME_stand_47        	240
+#define FRAME_stand_48        	241
+#define FRAME_stand_49        	242
+#define FRAME_stand_50        	243
+#define FRAME_stand_51        	244
+#define FRAME_stand_52        	245
+#define FRAME_stand_53        	246
+#define FRAME_stand_54        	247
+#define FRAME_stand_55        	248
+#define FRAME_stand_56        	249
+#define FRAME_stand_57        	250
+#define FRAME_stand_58        	251
+#define FRAME_stand_59        	252
+#define FRAME_stand_60        	253
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/m_tank.c
@@ -1,0 +1,833 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_tank.h"
+
+
+void tank_refire_rocket (edict_t *self);
+void tank_doattack_rocket (edict_t *self);
+void tank_reattack_blaster (edict_t *self);
+
+static int	sound_thud;
+static int	sound_pain;
+static int	sound_idle;
+static int	sound_die;
+static int	sound_step;
+static int	sound_sight;
+static int	sound_windup;
+static int	sound_strike;
+
+//
+// misc
+//
+
+void tank_sight (edict_t *self, edict_t *)
+{
+	gi.sound (self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
+}
+
+
+void tank_footstep (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0);
+}
+
+void tank_thud (edict_t *self)
+{
+	gi.sound (self, CHAN_BODY, sound_thud, 1, ATTN_NORM, 0);
+}
+
+void tank_windup (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
+}
+
+void tank_idle (edict_t *self)
+{
+	gi.sound (self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
+}
+
+
+//
+// stand
+//
+
+mframe_t tank_frames_stand []=
+{
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL,
+	ai_stand, 0, NULL
+};
+mmove_t	tank_move_stand = {FRAME_stand01, FRAME_stand30, tank_frames_stand, NULL};
+	
+void tank_stand (edict_t *self)
+{
+	self->monsterinfo.currentmove = &tank_move_stand;
+}
+
+
+//
+// walk
+//
+
+void tank_walk (edict_t *self);
+
+mframe_t tank_frames_start_walk [] =
+{
+	ai_walk,  0, NULL,
+	ai_walk,  6, NULL,
+	ai_walk,  6, NULL,
+	ai_walk, 11, tank_footstep
+};
+mmove_t	tank_move_start_walk = {FRAME_walk01, FRAME_walk04, tank_frames_start_walk, tank_walk};
+
+mframe_t tank_frames_walk [] =
+{
+	ai_walk, 4,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 3,	NULL,
+	ai_walk, 2,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 4,	tank_footstep,
+	ai_walk, 3,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 4,	NULL,
+	ai_walk, 5,	NULL,
+	ai_walk, 7,	NULL,
+	ai_walk, 7,	NULL,
+	ai_walk, 6,	NULL,
+	ai_walk, 6,	tank_footstep
+};
+mmove_t	tank_move_walk = {FRAME_walk05, FRAME_walk20, tank_frames_walk, NULL};
+
+mframe_t tank_frames_stop_walk [] =
+{
+	ai_walk,  3, NULL,
+	ai_walk,  3, NULL,
+	ai_walk,  2, NULL,
+	ai_walk,  2, NULL,
+	ai_walk,  4, tank_footstep
+};
+mmove_t	tank_move_stop_walk = {FRAME_walk21, FRAME_walk25, tank_frames_stop_walk, tank_stand};
+
+void tank_walk (edict_t *self)
+{
+		self->monsterinfo.currentmove = &tank_move_walk;
+}
+
+
+//
+// run
+//
+
+void tank_run (edict_t *self);
+
+mframe_t tank_frames_start_run [] =
+{
+	ai_run,  0, NULL,
+	ai_run,  6, NULL,
+	ai_run,  6, NULL,
+	ai_run, 11, tank_footstep
+};
+mmove_t	tank_move_start_run = {FRAME_walk01, FRAME_walk04, tank_frames_start_run, tank_run};
+
+mframe_t tank_frames_run [] =
+{
+	ai_run, 4,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 3,	NULL,
+	ai_run, 2,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 4,	NULL,
+	ai_run, 4,	tank_footstep,
+	ai_run, 3,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 4,	NULL,
+	ai_run, 5,	NULL,
+	ai_run, 7,	NULL,
+	ai_run, 7,	NULL,
+	ai_run, 6,	NULL,
+	ai_run, 6,	tank_footstep
+};
+mmove_t	tank_move_run = {FRAME_walk05, FRAME_walk20, tank_frames_run, NULL};
+
+mframe_t tank_frames_stop_run [] =
+{
+	ai_run,  3, NULL,
+	ai_run,  3, NULL,
+	ai_run,  2, NULL,
+	ai_run,  2, NULL,
+	ai_run,  4, tank_footstep
+};
+mmove_t	tank_move_stop_run = {FRAME_walk21, FRAME_walk25, tank_frames_stop_run, tank_walk};
+
+void tank_run (edict_t *self)
+{
+	if (self->enemy && self->enemy->client)
+		self->monsterinfo.aiflags |= AI_BRUTAL;
+	else
+		self->monsterinfo.aiflags &= ~AI_BRUTAL;
+
+	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
+	{
+		self->monsterinfo.currentmove = &tank_move_stand;
+		return;
+	}
+
+	if (self->monsterinfo.currentmove == &tank_move_walk ||
+		self->monsterinfo.currentmove == &tank_move_start_run)
+	{
+		self->monsterinfo.currentmove = &tank_move_run;
+	}
+	else
+	{
+		self->monsterinfo.currentmove = &tank_move_start_run;
+	}
+}
+
+//
+// pain
+//
+
+mframe_t tank_frames_pain1 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t tank_move_pain1 = {FRAME_pain101, FRAME_pain104, tank_frames_pain1, tank_run};
+
+mframe_t tank_frames_pain2 [] =
+{
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL,
+	ai_move, 0, NULL
+};
+mmove_t tank_move_pain2 = {FRAME_pain201, FRAME_pain205, tank_frames_pain2, tank_run};
+
+mframe_t tank_frames_pain3 [] =
+{
+	ai_move, -7, NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 3,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 2,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  NULL,
+	ai_move, 0,  tank_footstep
+};
+mmove_t	tank_move_pain3 = {FRAME_pain301, FRAME_pain316, tank_frames_pain3, tank_run};
+
+
+void tank_pain (edict_t *self, edict_t *, float, int damage)
+{
+	if (self->health < (self->max_health / 2))
+			self->s.skinnum |= 1;
+
+	if (damage <= 10)
+		return;
+
+	if (level.time < self->pain_debounce_time)
+			return;
+
+	if (damage <= 30)
+		if (qrandom() > 0.2)
+			return;
+	
+	// If hard or nightmare, don't go into pain while attacking
+	if ( skill->value >= 2)
+	{
+		if ( (self->s.frame >= FRAME_attak301) && (self->s.frame <= FRAME_attak330) )
+			return;
+		if ( (self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak116) )
+			return;
+	}
+
+	self->pain_debounce_time = level.time + 3;
+	gi.sound (self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
+
+	if (skill->value == 3)
+		return;		// no pain anims in nightmare
+
+	if (damage <= 30)
+		self->monsterinfo.currentmove = &tank_move_pain1;
+	else if (damage <= 60)
+		self->monsterinfo.currentmove = &tank_move_pain2;
+	else
+		self->monsterinfo.currentmove = &tank_move_pain3;
+};
+
+
+//
+// attacks
+//
+
+void TankBlaster (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	end;
+	vec3_t	dir;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak110)
+		flash_number = MZ2_TANK_BLASTER_1;
+	else if (self->s.frame == FRAME_attak113)
+		flash_number = MZ2_TANK_BLASTER_2;
+	else // (self->s.frame == FRAME_attak116)
+		flash_number = MZ2_TANK_BLASTER_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, end);
+	end[2] += self->enemy->viewheight;
+	VectorSubtract (end, start, dir);
+
+	monster_fire_blaster (self, start, dir, 30, 800, flash_number, EF_BLASTER);
+}	
+
+void TankStrike (edict_t *self)
+{
+	gi.sound (self, CHAN_WEAPON, sound_strike, 1, ATTN_NORM, 0);
+}	
+
+void TankRocket (edict_t *self)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	dir;
+	vec3_t	vec;
+	int		flash_number;
+
+	if (self->s.frame == FRAME_attak324)
+		flash_number = MZ2_TANK_ROCKET_1;
+	else if (self->s.frame == FRAME_attak327)
+		flash_number = MZ2_TANK_ROCKET_2;
+	else // (self->s.frame == FRAME_attak330)
+		flash_number = MZ2_TANK_ROCKET_3;
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	VectorCopy (self->enemy->s.origin, vec);
+	vec[2] += self->enemy->viewheight;
+	VectorSubtract (vec, start, dir);
+	VectorNormalize (dir);
+
+	monster_fire_rocket (self, start, dir, 50, 550, flash_number);
+}	
+
+void TankMachineGun (edict_t *self)
+{
+	vec3_t	dir;
+	vec3_t	vec;
+	vec3_t	start;
+	vec3_t	forward, right;
+	int		flash_number;
+
+	flash_number = MZ2_TANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak406);
+
+	AngleVectors (self->s.angles, forward, right, NULL);
+	G_ProjectSource (self->s.origin, monster_flash_offset[flash_number], forward, right, start);
+
+	if (self->enemy)
+	{
+		VectorCopy (self->enemy->s.origin, vec);
+		vec[2] += self->enemy->viewheight;
+		VectorSubtract (vec, start, vec);
+		vectoangles (vec, vec);
+		dir[0] = vec[0];
+	}
+	else
+	{
+		dir[0] = 0;
+	}
+	if (self->s.frame <= FRAME_attak415)
+		dir[1] = self->s.angles[1] - 8 * (self->s.frame - FRAME_attak411);
+	else
+		dir[1] = self->s.angles[1] + 8 * (self->s.frame - FRAME_attak419);
+	dir[2] = 0;
+
+	AngleVectors (dir, forward, NULL, NULL);
+
+	monster_fire_bullet (self, start, forward, 20, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
+}	
+
+
+mframe_t tank_frames_attack_blast [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -2,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, -1,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,		// 10
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster			// 16
+};
+mmove_t tank_move_attack_blast = {FRAME_attak101, FRAME_attak116, tank_frames_attack_blast, tank_reattack_blaster};
+
+mframe_t tank_frames_reattack_blast [] =
+{
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	NULL,
+	ai_charge, 0,	TankBlaster			// 16
+};
+mmove_t tank_move_reattack_blast = {FRAME_attak111, FRAME_attak116, tank_frames_reattack_blast, tank_reattack_blaster};
+
+mframe_t tank_frames_attack_post_blast [] =	
+{
+	ai_move, 0,		NULL,				// 17
+	ai_move, 0,		NULL,
+	ai_move, 2,		NULL,
+	ai_move, 3,		NULL,
+	ai_move, 2,		NULL,
+	ai_move, -2,	tank_footstep		// 22
+};
+mmove_t tank_move_attack_post_blast = {FRAME_attak117, FRAME_attak122, tank_frames_attack_post_blast, tank_run};
+
+void tank_reattack_blaster (edict_t *self)
+{
+	if (skill->value >= 2)
+		if (visible (self, self->enemy))
+			if (self->enemy->health > 0)
+				if (qrandom() <= 0.6)
+				{
+					self->monsterinfo.currentmove = &tank_move_reattack_blast;
+					return;
+				}
+	self->monsterinfo.currentmove = &tank_move_attack_post_blast;
+}
+
+
+void tank_poststrike (edict_t *self)
+{
+	self->enemy = NULL;
+	tank_run (self);
+}
+
+mframe_t tank_frames_attack_strike [] =
+{
+	ai_move, 3,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 6,   NULL,
+	ai_move, 7,   NULL,
+	ai_move, 9,   tank_footstep,
+	ai_move, 2,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 2,   tank_footstep,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 0,   tank_windup,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   TankStrike,
+	ai_move, 0,   NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -1,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -10, NULL,
+	ai_move, -10, NULL,
+	ai_move, -2,  NULL,
+	ai_move, -3,  NULL,
+	ai_move, -2,  tank_footstep
+};
+mmove_t tank_move_attack_strike = {FRAME_attak201, FRAME_attak238, tank_frames_attack_strike, tank_poststrike};
+
+mframe_t tank_frames_attack_pre_rocket [] =
+{
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 10
+
+	ai_charge, 0,  NULL,
+	ai_charge, 1,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 7,  NULL,
+	ai_charge, 7,  NULL,
+	ai_charge, 7,  tank_footstep,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 20
+
+	ai_charge, -3, NULL
+};
+mmove_t tank_move_attack_pre_rocket = {FRAME_attak301, FRAME_attak321, tank_frames_attack_pre_rocket, tank_doattack_rocket};
+
+mframe_t tank_frames_attack_fire_rocket [] =
+{
+	ai_charge, -3, NULL,			// Loop Start	22 
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  TankRocket,		// 24
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  TankRocket,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, -1, TankRocket		// 30	Loop End
+};
+mmove_t tank_move_attack_fire_rocket = {FRAME_attak322, FRAME_attak330, tank_frames_attack_fire_rocket, tank_refire_rocket};
+
+mframe_t tank_frames_attack_post_rocket [] =
+{	
+	ai_charge, 0,  NULL,			// 31
+	ai_charge, -1, NULL,
+	ai_charge, -1, NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 3,  NULL,
+	ai_charge, 4,  NULL,
+	ai_charge, 2,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 40
+
+	ai_charge, 0,  NULL,
+	ai_charge, -9, NULL,
+	ai_charge, -8, NULL,
+	ai_charge, -7, NULL,
+	ai_charge, -1, NULL,
+	ai_charge, -1, tank_footstep,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,			// 50
+
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL,
+	ai_charge, 0,  NULL
+};
+mmove_t tank_move_attack_post_rocket = {FRAME_attak331, FRAME_attak353, tank_frames_attack_post_rocket, tank_run};
+
+mframe_t tank_frames_attack_chain [] =
+{
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	NULL,      0, TankMachineGun,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL,
+	ai_charge, 0, NULL
+};
+mmove_t tank_move_attack_chain = {FRAME_attak401, FRAME_attak429, tank_frames_attack_chain, tank_run};
+
+void tank_refire_rocket (edict_t *self)
+{
+	// Only on hard or nightmare
+	if ( skill->value >= 2 )
+		if (self->enemy->health > 0)
+			if (visible(self, self->enemy) )
+				if (qrandom() <= 0.4)
+				{
+					self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+					return;
+				}
+	self->monsterinfo.currentmove = &tank_move_attack_post_rocket;
+}
+
+void tank_doattack_rocket (edict_t *self)
+{
+	self->monsterinfo.currentmove = &tank_move_attack_fire_rocket;
+}
+
+void tank_attack(edict_t *self)
+{
+	vec3_t	vec;
+	float	range;
+	float	r;
+
+	if (self->enemy->health < 0)
+	{
+		self->monsterinfo.currentmove = &tank_move_attack_strike;
+		self->monsterinfo.aiflags &= ~AI_BRUTAL;
+		return;
+	}
+
+	VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
+	range = VectorLength (vec);
+
+	r = qrandom();
+
+	if (range <= 125)
+	{
+		if (r < 0.4)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else 
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+	else if (range <= 250)
+	{
+		if (r < 0.5)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+	else
+	{
+		if (r < 0.33)
+			self->monsterinfo.currentmove = &tank_move_attack_chain;
+		else if (r < 0.66)
+		{
+			self->monsterinfo.currentmove = &tank_move_attack_pre_rocket;
+			self->pain_debounce_time = level.time + 5.0;	// no pain for a while
+		}
+		else
+			self->monsterinfo.currentmove = &tank_move_attack_blast;
+	}
+}
+
+
+//
+// death
+//
+
+void tank_dead (edict_t *self)
+{
+	VectorSet (self->mins, -16, -16, -16);
+	VectorSet (self->maxs, 16, 16, -0);
+	self->movetype = MOVETYPE_TOSS;
+	self->svflags |= SVF_DEADMONSTER;
+	self->nextthink = 0;
+	gi.linkentity (self);
+}
+
+mframe_t tank_frames_death1 [] =
+{
+	ai_move, -7,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, -2,  NULL,
+	ai_move, 1,   NULL,
+	ai_move, 3,   NULL,
+	ai_move, 6,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 1,   NULL,
+	ai_move, 2,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -2,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -3,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, -4,  NULL,
+	ai_move, -6,  NULL,
+	ai_move, -4,  NULL,
+	ai_move, -5,  NULL,
+	ai_move, -7,  NULL,
+	ai_move, -15, tank_thud,
+	ai_move, -5,  NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL,
+	ai_move, 0,   NULL
+};
+mmove_t	tank_move_death = {FRAME_death101, FRAME_death132, tank_frames_death1, tank_dead};
+
+void tank_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int		n;
+
+// check for gib
+	if (self->health <= self->gib_health)
+	{
+		gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 1 /*4*/; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_metal/tris.md2", damage, GIB_METALLIC);
+		ThrowGib (self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC);
+		ThrowHead (self, "models/objects/gibs/gear/tris.md2", damage, GIB_METALLIC);
+		self->deadflag = DEAD_DEAD;
+		return;
+	}
+
+	if (self->deadflag == DEAD_DEAD)
+		return;
+
+// regular death
+	gi.sound (self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
+	self->deadflag = DEAD_DEAD;
+	self->takedamage = DAMAGE_YES;
+
+	self->monsterinfo.currentmove = &tank_move_death;
+	
+}
+
+
+//
+// monster_tank
+//
+
+/*QUAKED monster_tank (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+*/
+/*QUAKED monster_tank_commander (1 .5 0) (-32 -32 -16) (32 32 72) Ambush Trigger_Spawn Sight
+*/
+void SP_monster_tank (edict_t *self)
+{
+	if (deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	self->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2");
+	VectorSet (self->mins, -32, -32, -16);
+	VectorSet (self->maxs, 32, 32, 72);
+	self->movetype = MOVETYPE_STEP;
+	self->solid = SOLID_BBOX;
+
+	sound_pain = gi.soundindex ("tank/tnkpain2.wav");
+	sound_thud = gi.soundindex ("tank/tnkdeth2.wav");
+	sound_idle = gi.soundindex ("tank/tnkidle1.wav");
+	sound_die = gi.soundindex ("tank/death.wav");
+	sound_step = gi.soundindex ("tank/step.wav");
+	sound_windup = gi.soundindex ("tank/tnkatck4.wav");
+	sound_strike = gi.soundindex ("tank/tnkatck5.wav");
+	sound_sight = gi.soundindex ("tank/sight1.wav");
+
+	gi.soundindex ("tank/tnkatck1.wav");
+	gi.soundindex ("tank/tnkatk2a.wav");
+	gi.soundindex ("tank/tnkatk2b.wav");
+	gi.soundindex ("tank/tnkatk2c.wav");
+	gi.soundindex ("tank/tnkatk2d.wav");
+	gi.soundindex ("tank/tnkatk2e.wav");
+	gi.soundindex ("tank/tnkatck3.wav");
+
+	if (strcmp(self->classname, "monster_tank_commander") == 0)
+	{
+		self->health = 1000;
+		self->gib_health = -225;
+	}
+	else
+	{
+		self->health = 750;
+		self->gib_health = -200;
+	}
+
+	self->mass = 500;
+
+	self->pain = tank_pain;
+	self->die = tank_die;
+	self->monsterinfo.stand = tank_stand;
+	self->monsterinfo.walk = tank_walk;
+	self->monsterinfo.run = tank_run;
+	self->monsterinfo.dodge = NULL;
+	self->monsterinfo.attack = tank_attack;
+	self->monsterinfo.melee = NULL;
+	self->monsterinfo.sight = tank_sight;
+	self->monsterinfo.idle = tank_idle;
+
+	gi.linkentity (self);
+	
+	self->monsterinfo.currentmove = &tank_move_stand;
+	self->monsterinfo.scale = MODEL_SCALE;
+
+	walkmonster_start(self);
+
+	if (strcmp(self->classname, "monster_tank_commander") == 0)
+		self->s.skinnum = 2;
+}
--- /dev/null
+++ b/xatrix/m_tank.h
@@ -1,0 +1,300 @@
+// G:\quake2\baseq2\models/monsters/tank
+
+// This file generated by qdata - Do NOT Modify
+
+#define FRAME_stand01         	0
+#define FRAME_stand02         	1
+#define FRAME_stand03         	2
+#define FRAME_stand04         	3
+#define FRAME_stand05         	4
+#define FRAME_stand06         	5
+#define FRAME_stand07         	6
+#define FRAME_stand08         	7
+#define FRAME_stand09         	8
+#define FRAME_stand10         	9
+#define FRAME_stand11         	10
+#define FRAME_stand12         	11
+#define FRAME_stand13         	12
+#define FRAME_stand14         	13
+#define FRAME_stand15         	14
+#define FRAME_stand16         	15
+#define FRAME_stand17         	16
+#define FRAME_stand18         	17
+#define FRAME_stand19         	18
+#define FRAME_stand20         	19
+#define FRAME_stand21         	20
+#define FRAME_stand22         	21
+#define FRAME_stand23         	22
+#define FRAME_stand24         	23
+#define FRAME_stand25         	24
+#define FRAME_stand26         	25
+#define FRAME_stand27         	26
+#define FRAME_stand28         	27
+#define FRAME_stand29         	28
+#define FRAME_stand30         	29
+#define FRAME_walk01          	30
+#define FRAME_walk02          	31
+#define FRAME_walk03          	32
+#define FRAME_walk04          	33
+#define FRAME_walk05          	34
+#define FRAME_walk06          	35
+#define FRAME_walk07          	36
+#define FRAME_walk08          	37
+#define FRAME_walk09          	38
+#define FRAME_walk10          	39
+#define FRAME_walk11          	40
+#define FRAME_walk12          	41
+#define FRAME_walk13          	42
+#define FRAME_walk14          	43
+#define FRAME_walk15          	44
+#define FRAME_walk16          	45
+#define FRAME_walk17          	46
+#define FRAME_walk18          	47
+#define FRAME_walk19          	48
+#define FRAME_walk20          	49
+#define FRAME_walk21          	50
+#define FRAME_walk22          	51
+#define FRAME_walk23          	52
+#define FRAME_walk24          	53
+#define FRAME_walk25          	54
+#define FRAME_attak101        	55
+#define FRAME_attak102        	56
+#define FRAME_attak103        	57
+#define FRAME_attak104        	58
+#define FRAME_attak105        	59
+#define FRAME_attak106        	60
+#define FRAME_attak107        	61
+#define FRAME_attak108        	62
+#define FRAME_attak109        	63
+#define FRAME_attak110        	64
+#define FRAME_attak111        	65
+#define FRAME_attak112        	66
+#define FRAME_attak113        	67
+#define FRAME_attak114        	68
+#define FRAME_attak115        	69
+#define FRAME_attak116        	70
+#define FRAME_attak117        	71
+#define FRAME_attak118        	72
+#define FRAME_attak119        	73
+#define FRAME_attak120        	74
+#define FRAME_attak121        	75
+#define FRAME_attak122        	76
+#define FRAME_attak201        	77
+#define FRAME_attak202        	78
+#define FRAME_attak203        	79
+#define FRAME_attak204        	80
+#define FRAME_attak205        	81
+#define FRAME_attak206        	82
+#define FRAME_attak207        	83
+#define FRAME_attak208        	84
+#define FRAME_attak209        	85
+#define FRAME_attak210        	86
+#define FRAME_attak211        	87
+#define FRAME_attak212        	88
+#define FRAME_attak213        	89
+#define FRAME_attak214        	90
+#define FRAME_attak215        	91
+#define FRAME_attak216        	92
+#define FRAME_attak217        	93
+#define FRAME_attak218        	94
+#define FRAME_attak219        	95
+#define FRAME_attak220        	96
+#define FRAME_attak221        	97
+#define FRAME_attak222        	98
+#define FRAME_attak223        	99
+#define FRAME_attak224        	100
+#define FRAME_attak225        	101
+#define FRAME_attak226        	102
+#define FRAME_attak227        	103
+#define FRAME_attak228        	104
+#define FRAME_attak229        	105
+#define FRAME_attak230        	106
+#define FRAME_attak231        	107
+#define FRAME_attak232        	108
+#define FRAME_attak233        	109
+#define FRAME_attak234        	110
+#define FRAME_attak235        	111
+#define FRAME_attak236        	112
+#define FRAME_attak237        	113
+#define FRAME_attak238        	114
+#define FRAME_attak301        	115
+#define FRAME_attak302        	116
+#define FRAME_attak303        	117
+#define FRAME_attak304        	118
+#define FRAME_attak305        	119
+#define FRAME_attak306        	120
+#define FRAME_attak307        	121
+#define FRAME_attak308        	122
+#define FRAME_attak309        	123
+#define FRAME_attak310        	124
+#define FRAME_attak311        	125
+#define FRAME_attak312        	126
+#define FRAME_attak313        	127
+#define FRAME_attak314        	128
+#define FRAME_attak315        	129
+#define FRAME_attak316        	130
+#define FRAME_attak317        	131
+#define FRAME_attak318        	132
+#define FRAME_attak319        	133
+#define FRAME_attak320        	134
+#define FRAME_attak321        	135
+#define FRAME_attak322        	136
+#define FRAME_attak323        	137
+#define FRAME_attak324        	138
+#define FRAME_attak325        	139
+#define FRAME_attak326        	140
+#define FRAME_attak327        	141
+#define FRAME_attak328        	142
+#define FRAME_attak329        	143
+#define FRAME_attak330        	144
+#define FRAME_attak331        	145
+#define FRAME_attak332        	146
+#define FRAME_attak333        	147
+#define FRAME_attak334        	148
+#define FRAME_attak335        	149
+#define FRAME_attak336        	150
+#define FRAME_attak337        	151
+#define FRAME_attak338        	152
+#define FRAME_attak339        	153
+#define FRAME_attak340        	154
+#define FRAME_attak341        	155
+#define FRAME_attak342        	156
+#define FRAME_attak343        	157
+#define FRAME_attak344        	158
+#define FRAME_attak345        	159
+#define FRAME_attak346        	160
+#define FRAME_attak347        	161
+#define FRAME_attak348        	162
+#define FRAME_attak349        	163
+#define FRAME_attak350        	164
+#define FRAME_attak351        	165
+#define FRAME_attak352        	166
+#define FRAME_attak353        	167
+#define FRAME_attak401        	168
+#define FRAME_attak402        	169
+#define FRAME_attak403        	170
+#define FRAME_attak404        	171
+#define FRAME_attak405        	172
+#define FRAME_attak406        	173
+#define FRAME_attak407        	174
+#define FRAME_attak408        	175
+#define FRAME_attak409        	176
+#define FRAME_attak410        	177
+#define FRAME_attak411        	178
+#define FRAME_attak412        	179
+#define FRAME_attak413        	180
+#define FRAME_attak414        	181
+#define FRAME_attak415        	182
+#define FRAME_attak416        	183
+#define FRAME_attak417        	184
+#define FRAME_attak418        	185
+#define FRAME_attak419        	186
+#define FRAME_attak420        	187
+#define FRAME_attak421        	188
+#define FRAME_attak422        	189
+#define FRAME_attak423        	190
+#define FRAME_attak424        	191
+#define FRAME_attak425        	192
+#define FRAME_attak426        	193
+#define FRAME_attak427        	194
+#define FRAME_attak428        	195
+#define FRAME_attak429        	196
+#define FRAME_pain101         	197
+#define FRAME_pain102         	198
+#define FRAME_pain103         	199
+#define FRAME_pain104         	200
+#define FRAME_pain201         	201
+#define FRAME_pain202         	202
+#define FRAME_pain203         	203
+#define FRAME_pain204         	204
+#define FRAME_pain205         	205
+#define FRAME_pain301         	206
+#define FRAME_pain302         	207
+#define FRAME_pain303         	208
+#define FRAME_pain304         	209
+#define FRAME_pain305         	210
+#define FRAME_pain306         	211
+#define FRAME_pain307         	212
+#define FRAME_pain308         	213
+#define FRAME_pain309         	214
+#define FRAME_pain310         	215
+#define FRAME_pain311         	216
+#define FRAME_pain312         	217
+#define FRAME_pain313         	218
+#define FRAME_pain314         	219
+#define FRAME_pain315         	220
+#define FRAME_pain316         	221
+#define FRAME_death101        	222
+#define FRAME_death102        	223
+#define FRAME_death103        	224
+#define FRAME_death104        	225
+#define FRAME_death105        	226
+#define FRAME_death106        	227
+#define FRAME_death107        	228
+#define FRAME_death108        	229
+#define FRAME_death109        	230
+#define FRAME_death110        	231
+#define FRAME_death111        	232
+#define FRAME_death112        	233
+#define FRAME_death113        	234
+#define FRAME_death114        	235
+#define FRAME_death115        	236
+#define FRAME_death116        	237
+#define FRAME_death117        	238
+#define FRAME_death118        	239
+#define FRAME_death119        	240
+#define FRAME_death120        	241
+#define FRAME_death121        	242
+#define FRAME_death122        	243
+#define FRAME_death123        	244
+#define FRAME_death124        	245
+#define FRAME_death125        	246
+#define FRAME_death126        	247
+#define FRAME_death127        	248
+#define FRAME_death128        	249
+#define FRAME_death129        	250
+#define FRAME_death130        	251
+#define FRAME_death131        	252
+#define FRAME_death132        	253
+#define FRAME_recln101        	254
+#define FRAME_recln102        	255
+#define FRAME_recln103        	256
+#define FRAME_recln104        	257
+#define FRAME_recln105        	258
+#define FRAME_recln106        	259
+#define FRAME_recln107        	260
+#define FRAME_recln108        	261
+#define FRAME_recln109        	262
+#define FRAME_recln110        	263
+#define FRAME_recln111        	264
+#define FRAME_recln112        	265
+#define FRAME_recln113        	266
+#define FRAME_recln114        	267
+#define FRAME_recln115        	268
+#define FRAME_recln116        	269
+#define FRAME_recln117        	270
+#define FRAME_recln118        	271
+#define FRAME_recln119        	272
+#define FRAME_recln120        	273
+#define FRAME_recln121        	274
+#define FRAME_recln122        	275
+#define FRAME_recln123        	276
+#define FRAME_recln124        	277
+#define FRAME_recln125        	278
+#define FRAME_recln126        	279
+#define FRAME_recln127        	280
+#define FRAME_recln128        	281
+#define FRAME_recln129        	282
+#define FRAME_recln130        	283
+#define FRAME_recln131        	284
+#define FRAME_recln132        	285
+#define FRAME_recln133        	286
+#define FRAME_recln134        	287
+#define FRAME_recln135        	288
+#define FRAME_recln136        	289
+#define FRAME_recln137        	290
+#define FRAME_recln138        	291
+#define FRAME_recln139        	292
+#define FRAME_recln140        	293
+
+#define MODEL_SCALE		1.000000
--- /dev/null
+++ b/xatrix/mkfile
@@ -1,0 +1,87 @@
+</$objtype/mkfile
+
+LIB=xatrix.$O.a
+
+OFILES=\
+	g_ai.$O\
+	g_chase.$O\
+	g_cmds.$O\
+	g_combat.$O\
+	g_func.$O\
+	g_items.$O\
+	g_main.$O\
+	g_misc.$O\
+	g_monster.$O\
+	g_phys.$O\
+	g_save.$O\
+	g_spawn.$O\
+	g_svcmds.$O\
+	g_target.$O\
+	g_trigger.$O\
+	g_turret.$O\
+	g_utils.$O\
+	g_weapon.$O\
+	m_actor.$O\
+	m_berserk.$O\
+	m_boss2.$O\
+	m_boss3.$O\
+	m_boss31.$O\
+	m_boss32.$O\
+	m_boss5.$O\
+	m_brain.$O\
+	m_chick.$O\
+	m_fixbot.$O\
+	m_flash.$O\
+	m_flipper.$O\
+	m_float.$O\
+	m_flyer.$O\
+	m_gekk.$O\
+	m_gladb.$O\
+	m_gladiator.$O\
+	m_gunner.$O\
+	m_hover.$O\
+	m_infantry.$O\
+	m_insane.$O\
+	m_medic.$O\
+	m_move.$O\
+	m_mutant.$O\
+	m_parasite.$O\
+	m_soldier.$O\
+	m_supertank.$O\
+	m_tank.$O\
+	p_client.$O\
+	p_hud.$O\
+	p_trail.$O\
+	p_view.$O\
+	p_weapon.$O\
+	q_shared.$O\
+
+HFILES=\
+	game.h\
+	m_actor.h\
+	m_berserk.h\
+	m_boss2.h\
+	m_boss31.h\
+	m_boss32.h\
+	m_brain.h\
+	m_chick.h\
+	m_fixbot.h\
+	m_flipper.h\
+	m_float.h\
+	m_flyer.h\
+	m_gekk.h\
+	m_gladiator.h\
+	m_gunner.h\
+	m_hover.h\
+	m_infantry.h\
+	m_insane.h\
+	m_medic.h\
+	m_mutant.h\
+	m_parasite.h\
+	m_player.h\
+	m_rider.h\
+	m_soldier.h\
+	m_supertank.h\
+	m_tank.h\
+
+</sys/src/cmd/mklib
--- /dev/null
+++ b/xatrix/p_client.c
@@ -1,0 +1,1856 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+void ClientUserinfoChanged (edict_t *ent, char *userinfo);
+
+void SP_misc_teleporter_dest (edict_t *ent);
+
+//
+// Gross, ugly, disgustuing hack section
+//
+
+// this function is an ugly as hell hack to fix some map flaws
+//
+// the coop spawn spots on some maps are SNAFU.  There are coop spots
+// with the wrong targetname as well as spots with no name at all
+//
+// we use carnal knowledge of the maps to fix the coop spot targetnames to match
+// that of the nearest named single player spot
+
+static void SP_FixCoopSpots (edict_t *self)
+{
+	edict_t	*spot;
+	vec3_t	d;
+
+	spot = NULL;
+
+	while(1)
+	{
+		spot = G_Find(spot, FOFS(classname), "info_player_start");
+		if (!spot)
+			return;
+		if (!spot->targetname)
+			continue;
+		VectorSubtract(self->s.origin, spot->s.origin, d);
+		if (VectorLength(d) < 384)
+		{
+			if ((!self->targetname) || cistrcmp(self->targetname, spot->targetname) != 0)
+			{
+//				gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname);
+				self->targetname = spot->targetname;
+			}
+			return;
+		}
+	}
+}
+
+// now if that one wasn't ugly enough for you then try this one on for size
+// some maps don't have any coop spots at all, so we need to create them
+// where they should have been
+
+static void SP_CreateCoopSpots (edict_t *)
+{
+	edict_t	*spot;
+
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 - 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 64;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		spot = G_Spawn();
+		spot->classname = "info_player_coop";
+		spot->s.origin[0] = 188 + 128;
+		spot->s.origin[1] = -164;
+		spot->s.origin[2] = 80;
+		spot->targetname = "jail3";
+		spot->s.angles[1] = 90;
+
+		return;
+	}
+}
+
+
+/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
+The normal starting point for a level.
+*/
+void SP_info_player_start(edict_t *self)
+{
+	if (!coop->value)
+		return;
+	if(cistrcmp(level.mapname, "security") == 0)
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_CreateCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for deathmatch games
+*/
+void SP_info_player_deathmatch(edict_t *self)
+{
+	if (!deathmatch->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+	SP_misc_teleporter_dest (self);
+}
+
+/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32)
+potential spawning position for coop games
+*/
+
+void SP_info_player_coop(edict_t *self)
+{
+	if (!coop->value)
+	{
+		G_FreeEdict (self);
+		return;
+	}
+
+	if((cistrcmp(level.mapname, "jail2") == 0)   ||
+	   (cistrcmp(level.mapname, "jail4") == 0)   ||
+	   (cistrcmp(level.mapname, "mine1") == 0)   ||
+	   (cistrcmp(level.mapname, "mine2") == 0)   ||
+	   (cistrcmp(level.mapname, "mine3") == 0)   ||
+	   (cistrcmp(level.mapname, "mine4") == 0)   ||
+	   (cistrcmp(level.mapname, "lab") == 0)     ||
+	   (cistrcmp(level.mapname, "boss1") == 0)   ||
+	   (cistrcmp(level.mapname, "fact3") == 0)   ||
+	   (cistrcmp(level.mapname, "biggun") == 0)  ||
+	   (cistrcmp(level.mapname, "space") == 0)   ||
+	   (cistrcmp(level.mapname, "command") == 0) ||
+	   (cistrcmp(level.mapname, "power2") == 0) ||
+	   (cistrcmp(level.mapname, "strike") == 0))
+	{
+		// invoke one of our gross, ugly, disgusting hacks
+		self->think = SP_FixCoopSpots;
+		self->nextthink = level.time + FRAMETIME;
+	}
+}
+
+
+/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
+The deathmatch intermission point will be at one of these
+Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw.  'pitch yaw roll'
+*/
+void SP_info_player_intermission(edict_t *)
+{
+}
+
+
+//=======================================================================
+
+
+void player_pain (edict_t *, edict_t *, float, int)
+{
+	// player pain is handled at the end of the frame in P_DamageFeedback
+}
+
+
+qboolean IsFemale (edict_t *ent)
+{
+	char		*info;
+
+	if (!ent->client)
+		return false;
+
+	info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+	if (info[0] == 'f' || info[0] == 'F')
+		return true;
+	return false;
+}
+
+qboolean IsNeutral (edict_t *ent)
+{
+	char		*info;
+
+	if (!ent->client)
+		return false;
+
+	info = Info_ValueForKey (ent->client->pers.userinfo, "gender");
+	if (info[0] != 'f' && info[0] != 'F' && info[0] != 'm' && info[0] != 'M')
+		return true;
+	return false;
+}
+
+void ClientObituary (edict_t *self, edict_t *, edict_t *attacker)
+{
+	int			mod;
+	char		*message;
+	char		*message2;
+	qboolean	ff;
+
+	if (coop->value && attacker->client)
+		meansOfDeath |= MOD_FRIENDLY_FIRE;
+
+	if (deathmatch->value || coop->value)
+	{
+		ff = meansOfDeath & MOD_FRIENDLY_FIRE;
+		mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
+		message = NULL;
+		message2 = "";
+
+		switch (mod)
+		{
+		case MOD_SUICIDE:
+			message = "suicides";
+			break;
+		case MOD_FALLING:
+			message = "cratered";
+			break;
+		case MOD_CRUSH:
+			message = "was squished";
+			break;
+		case MOD_WATER:
+			message = "sank like a rock";
+			break;
+		case MOD_SLIME:
+			message = "melted";
+			break;
+		case MOD_LAVA:
+			message = "does a back flip into the lava";
+			break;
+		case MOD_EXPLOSIVE:
+		case MOD_BARREL:
+			message = "blew up";
+			break;
+		case MOD_EXIT:
+			message = "found a way out";
+			break;
+		case MOD_TARGET_LASER:
+			message = "saw the light";
+			break;
+		case MOD_TARGET_BLASTER:
+			message = "got blasted";
+			break;
+		case MOD_BOMB:
+		case MOD_SPLASH:
+		case MOD_TRIGGER_HURT:
+			message = "was in the wrong place";
+			break;
+		// RAFAEL
+		case MOD_GEKK:
+		case MOD_BRAINTENTACLE:
+			message = "that's gotta hurt";
+			break;
+		}
+		if (attacker == self)
+		{
+			switch (mod)
+			{
+			case MOD_HELD_GRENADE:
+				message = "tried to put the pin back in";
+				break;
+			case MOD_HG_SPLASH:
+			case MOD_G_SPLASH:
+				if (IsNeutral(self))
+					message = "tripped on its own grenade";
+				else if (IsFemale(self))
+					message = "tripped on her own grenade";
+				else
+					message = "tripped on his own grenade";
+				break;
+			case MOD_R_SPLASH:
+				if (IsNeutral(self))
+					message = "blew itself up";
+				else if (IsFemale(self))
+					message = "blew herself up";
+				else
+					message = "blew himself up";
+				break;
+			case MOD_BFG_BLAST:
+				message = "should have used a smaller gun";
+				break;
+			// RAFAEL 03-MAY-98
+			case MOD_TRAP:
+			 	message = "sucked into his own trap";
+				break;
+			default:
+				if (IsNeutral(self))
+					message = "killed itself";
+				else if (IsFemale(self))
+					message = "killed herself";
+				else
+					message = "killed himself";
+				break;
+			}
+		}
+		if (message)
+		{
+			gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message);
+			if (deathmatch->value)
+				self->client->resp.score--;
+			self->enemy = NULL;
+			return;
+		}
+
+		self->enemy = attacker;
+		if (attacker && attacker->client)
+		{
+			switch (mod)
+			{
+			case MOD_BLASTER:
+				message = "was blasted by";
+				break;
+			case MOD_SHOTGUN:
+				message = "was gunned down by";
+				break;
+			case MOD_SSHOTGUN:
+				message = "was blown away by";
+				message2 = "'s super shotgun";
+				break;
+			case MOD_MACHINEGUN:
+				message = "was machinegunned by";
+				break;
+			case MOD_CHAINGUN:
+				message = "was cut in half by";
+				message2 = "'s chaingun";
+				break;
+			case MOD_GRENADE:
+				message = "was popped by";
+				message2 = "'s grenade";
+				break;
+			case MOD_G_SPLASH:
+				message = "was shredded by";
+				message2 = "'s shrapnel";
+				break;
+			case MOD_ROCKET:
+				message = "ate";
+				message2 = "'s rocket";
+				break;
+			case MOD_R_SPLASH:
+				message = "almost dodged";
+				message2 = "'s rocket";
+				break;
+			case MOD_HYPERBLASTER:
+				message = "was melted by";
+				message2 = "'s hyperblaster";
+				break;
+			case MOD_RAILGUN:
+				message = "was railed by";
+				break;
+			case MOD_BFG_LASER:
+				message = "saw the pretty lights from";
+				message2 = "'s BFG";
+				break;
+			case MOD_BFG_BLAST:
+				message = "was disintegrated by";
+				message2 = "'s BFG blast";
+				break;
+			case MOD_BFG_EFFECT:
+				message = "couldn't hide from";
+				message2 = "'s BFG";
+				break;
+			case MOD_HANDGRENADE:
+				message = "caught";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HG_SPLASH:
+				message = "didn't see";
+				message2 = "'s handgrenade";
+				break;
+			case MOD_HELD_GRENADE:
+				message = "feels";
+				message2 = "'s pain";
+				break;
+			case MOD_TELEFRAG:
+				message = "tried to invade";
+				message2 = "'s personal space";
+				break;
+			// RAFAEL 14-APR-98
+			case MOD_RIPPER:
+				message = "ripped to shreds by";
+				message2 = "'s ripper gun";
+				break;
+			case MOD_PHALANX:
+				message = "was evaporated by";
+				break;
+			case MOD_TRAP:
+				message = "caught in trap by";
+				break;
+			// END 14-APR-98
+			}
+			if (message)
+			{
+				gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
+				if (deathmatch->value)
+				{
+					if (ff)
+						attacker->client->resp.score--;
+					else
+						attacker->client->resp.score++;
+				}
+				return;
+			}
+		}
+	}
+
+	gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname);
+	if (deathmatch->value)
+		self->client->resp.score--;
+}
+
+
+void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf);
+
+void TossClientWeapon (edict_t *self)
+{
+	gitem_t		*item;
+	edict_t		*drop;
+	qboolean	quad;
+	// RAFAEL
+	qboolean	quadfire;
+	float		spread;
+
+	if (!deathmatch->value)
+		return;
+
+	item = self->client->pers.weapon;
+	if (! self->client->pers.inventory[self->client->ammo_index] )
+		item = NULL;
+	if (item && (strcmp (item->pickup_name, "Blaster") == 0))
+		item = NULL;
+
+	if (!((int)(dmflags->value) & DF_QUAD_DROP))
+		quad = false;
+	else
+		quad = (self->client->quad_framenum > (level.framenum + 10));
+
+	// RAFAEL
+	if (!((int)(dmflags->value) & DF_QUADFIRE_DROP))
+		quadfire = false;
+	else
+		quadfire = (self->client->quadfire_framenum > (level.framenum + 10));
+
+	
+	if (item && quad)
+		spread = 22.5;
+	else if (item && quadfire)
+		spread = 12.5;
+	else
+		spread = 0.0;
+
+	if (item)
+	{
+		self->client->v_angle[YAW] -= spread;
+		drop = Drop_Item (self, item);
+		self->client->v_angle[YAW] += spread;
+		drop->spawnflags = DROPPED_PLAYER_ITEM;
+	}
+
+	if (quad)
+	{
+		self->client->v_angle[YAW] += spread;
+		drop = Drop_Item (self, FindItemByClassname ("item_quad"));
+		self->client->v_angle[YAW] -= spread;
+		drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+		drop->touch = Touch_Item;
+		drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME;
+		drop->think = G_FreeEdict;
+	}
+
+	// RAFAEL
+	if (quadfire)
+	{
+		self->client->v_angle[YAW] += spread;
+		drop = Drop_Item (self, FindItemByClassname ("item_quadfire"));
+		self->client->v_angle[YAW] -= spread;
+		drop->spawnflags |= DROPPED_PLAYER_ITEM;
+
+		drop->touch = Touch_Item;
+		drop->nextthink = level.time + (self->client->quadfire_framenum - level.framenum) * FRAMETIME;
+		drop->think = G_FreeEdict;
+	}
+}
+
+
+/*
+==================
+LookAtKiller
+==================
+*/
+void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker)
+{
+	vec3_t		dir;
+
+	if (attacker && attacker != WORLD && attacker != self)
+	{
+		VectorSubtract (attacker->s.origin, self->s.origin, dir);
+	}
+	else if (inflictor && inflictor != WORLD && inflictor != self)
+	{
+		VectorSubtract (inflictor->s.origin, self->s.origin, dir);
+	}
+	else
+	{
+		self->client->killer_yaw = self->s.angles[YAW];
+		return;
+	}
+
+	if (dir[0])
+		self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]);
+	else {
+		self->client->killer_yaw = 0;
+		if (dir[1] > 0)
+			self->client->killer_yaw = 90;
+		else if (dir[1] < 0)
+			self->client->killer_yaw = -90;
+	}
+	if (self->client->killer_yaw < 0)
+		self->client->killer_yaw += 360;
+}
+
+/*
+==================
+player_die
+==================
+*/
+void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t)
+{
+	int		n;
+
+	VectorClear (self->avelocity);
+
+	self->takedamage = DAMAGE_YES;
+	self->movetype = MOVETYPE_TOSS;
+
+	self->s.modelindex2 = 0;	// remove linked weapon model
+
+	self->s.angles[0] = 0;
+	self->s.angles[2] = 0;
+
+	self->s.sound = 0;
+	self->client->weapon_sound = 0;
+
+	self->maxs[2] = -8;
+
+//	self->solid = SOLID_NOT;
+	self->svflags |= SVF_DEADMONSTER;
+
+	if (!self->deadflag)
+	{
+		self->client->respawn_time = level.time + 1.0;
+		LookAtKiller (self, inflictor, attacker);
+		self->client->ps.pmove.pm_type = PM_DEAD;
+		ClientObituary (self, inflictor, attacker);
+		TossClientWeapon (self);
+		if (deathmatch->value)
+			Cmd_Help_f (self);		// show scores
+
+		// clear inventory
+		// this is kind of ugly, but it's how we want to handle keys in coop
+		for (n = 0; n < game.num_items; n++)
+		{
+			if (coop->value && itemlist[n].flags & IT_KEY)
+				self->client->resp.coop_respawn.inventory[n] = self->client->pers.inventory[n];
+			self->client->pers.inventory[n] = 0;
+		}
+	}
+
+	// remove powerups
+	self->client->quad_framenum = 0;
+	self->client->invincible_framenum = 0;
+	self->client->breather_framenum = 0;
+	self->client->enviro_framenum = 0;
+	self->flags &= ~FL_POWER_ARMOR;
+
+	// RAFAEL
+	self->client->quadfire_framenum = 0;
+
+	if (self->health < -40)
+	{	// gib
+		gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		ThrowClientHead (self, damage);
+
+		self->takedamage = DAMAGE_NO;
+	}
+	else
+	{	// normal death
+		if (!self->deadflag)
+		{
+			static int i;
+
+			i = (i+1)%3;
+			// start a death animation
+			self->client->anim_priority = ANIM_DEATH;
+			if (self->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				self->s.frame = FRAME_crdeath1-1;
+				self->client->anim_end = FRAME_crdeath5;
+			}
+			else switch (i)
+			{
+			case 0:
+				self->s.frame = FRAME_death101-1;
+				self->client->anim_end = FRAME_death106;
+				break;
+			case 1:
+				self->s.frame = FRAME_death201-1;
+				self->client->anim_end = FRAME_death206;
+				break;
+			case 2:
+				self->s.frame = FRAME_death301-1;
+				self->client->anim_end = FRAME_death308;
+				break;
+			}
+			gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0);
+		}
+	}
+
+	self->deadflag = DEAD_DEAD;
+
+	gi.linkentity (self);
+}
+
+//=======================================================================
+
+/*
+==============
+InitClientPersistant
+
+This is only called when the game first initializes in single player,
+but is called after each death and level change in deathmatch
+==============
+*/
+void InitClientPersistant (gclient_t *client)
+{
+	gitem_t		*item;
+
+//	gi.dprintf("InitClientPersistant()\n");
+
+	memset (&client->pers, 0, sizeof(client->pers));
+
+	item = FindItem("Blaster");
+	client->pers.selected_item = ITEM_INDEX(item);
+	client->pers.inventory[client->pers.selected_item] = 1;
+
+	client->pers.weapon = item;
+
+	client->pers.health			= 100;
+	client->pers.max_health		= 100;
+
+	client->pers.max_bullets	= 200;
+	client->pers.max_shells		= 100;
+	client->pers.max_rockets	= 50;
+	client->pers.max_grenades	= 50;
+	client->pers.max_cells		= 200;
+	client->pers.max_slugs		= 50;
+
+	// RAFAEL
+	client->pers.max_magslug	= 50;
+	client->pers.max_trap		= 5;
+
+	client->pers.connected = true;
+}
+
+
+void InitClientResp (gclient_t *client)
+{
+	memset (&client->resp, 0, sizeof(client->resp));
+	client->resp.enterframe = level.framenum;
+	client->resp.coop_respawn = client->pers;
+}
+
+/*
+==================
+SaveClientData
+
+Some information that should be persistant, like health, 
+is still stored in the edict structure, so it needs to
+be mirrored out to the client structure before all the
+edicts are wiped.
+==================
+*/
+void SaveClientData (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		ent = &g_edicts[1+i];
+		if (!ent->inuse)
+			continue;
+		game.clients[i].pers.health = ent->health;
+		game.clients[i].pers.max_health = ent->max_health;
+		game.clients[i].pers.savedFlags = (ent->flags & (FL_GODMODE|FL_NOTARGET|FL_POWER_ARMOR));
+		if (coop->value)
+			game.clients[i].pers.score = ent->client->resp.score;
+	}
+}
+
+void FetchClientEntData (edict_t *ent)
+{
+	ent->health = ent->client->pers.health;
+	ent->max_health = ent->client->pers.max_health;
+	ent->flags |= ent->client->pers.savedFlags;
+	if (coop->value)
+		ent->client->resp.score = ent->client->pers.score;
+}
+
+
+
+/*
+=======================================================================
+
+  SelectSpawnPoint
+
+=======================================================================
+*/
+
+/*
+================
+PlayersRangeFromSpot
+
+Returns the distance to the nearest player from the given spot
+================
+*/
+float	PlayersRangeFromSpot (edict_t *spot)
+{
+	edict_t	*player;
+	float	bestplayerdistance;
+	vec3_t	v;
+	int		n;
+	float	playerdistance;
+
+
+	bestplayerdistance = 9999999;
+
+	for (n = 1; n <= maxclients->value; n++)
+	{
+		player = &g_edicts[n];
+
+		if (!player->inuse)
+			continue;
+
+		if (player->health <= 0)
+			continue;
+
+		VectorSubtract (spot->s.origin, player->s.origin, v);
+		playerdistance = VectorLength (v);
+
+		if (playerdistance < bestplayerdistance)
+			bestplayerdistance = playerdistance;
+	}
+
+	return bestplayerdistance;
+}
+
+/*
+================
+SelectRandomDeathmatchSpawnPoint
+
+go to a random point, but NOT the two points closest
+to other players
+================
+*/
+edict_t *SelectRandomDeathmatchSpawnPoint (void)
+{
+	edict_t	*spot, *spot1, *spot2;
+	int		count = 0;
+	int		selection;
+	float	range, range1, range2;
+
+	spot = NULL;
+	range1 = range2 = 99999;
+	spot1 = spot2 = NULL;
+
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		count++;
+		range = PlayersRangeFromSpot(spot);
+		if (range < range1)
+		{
+			range1 = range;
+			spot1 = spot;
+		}
+		else if (range < range2)
+		{
+			range2 = range;
+			spot2 = spot;
+		}
+	}
+
+	if (!count)
+		return NULL;
+
+	if (count <= 2)
+	{
+		spot1 = spot2 = NULL;
+	}
+	else
+		count -= 2;
+
+	selection = rand() % count;
+
+	spot = NULL;
+	do
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
+		if (spot == spot1 || spot == spot2)
+			selection++;
+	} while(selection--);
+
+	return spot;
+}
+
+/*
+================
+SelectFarthestDeathmatchSpawnPoint
+
+================
+*/
+edict_t *SelectFarthestDeathmatchSpawnPoint (void)
+{
+	edict_t	*bestspot;
+	float	bestdistance, bestplayerdistance;
+	edict_t	*spot;
+
+
+	spot = NULL;
+	bestspot = NULL;
+	bestdistance = 0;
+	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
+	{
+		bestplayerdistance = PlayersRangeFromSpot (spot);
+
+		if (bestplayerdistance > bestdistance)
+		{
+			bestspot = spot;
+			bestdistance = bestplayerdistance;
+		}
+	}
+
+	if (bestspot)
+	{
+		return bestspot;
+	}
+
+	// if there is a player just spawned on each and every start spot
+	// we have no choice to turn one into a telefrag meltdown
+	spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+
+	return spot;
+}
+
+edict_t *SelectDeathmatchSpawnPoint (void)
+{
+	if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
+		return SelectFarthestDeathmatchSpawnPoint ();
+	else
+		return SelectRandomDeathmatchSpawnPoint ();
+}
+
+
+edict_t *SelectCoopSpawnPoint (edict_t *ent)
+{
+	int		index;
+	edict_t	*spot;
+	char	*target;
+
+	index = ent->client - game.clients;
+
+	// player 0 starts in normal player spawn point
+	if (!index)
+		return NULL;
+
+	spot = NULL;
+
+	// assume there are four coop spots at each spawnpoint
+	while (1)
+	{
+		spot = G_Find (spot, FOFS(classname), "info_player_coop");
+		if (!spot)
+			return NULL;	// we didn't have enough...
+
+		target = spot->targetname;
+		if (!target)
+			target = "";
+		if ( cistrcmp(game.spawnpoint, target) == 0 )
+		{	// this is a coop spawn point for one of the clients here
+			index--;
+			if (!index)
+				return spot;		// this is it
+		}
+	}
+}
+
+
+/*
+===========
+SelectSpawnPoint
+
+Chooses a player start, deathmatch start, coop start, etc
+============
+*/
+void	SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles)
+{
+	edict_t	*spot = NULL;
+
+	if (deathmatch->value)
+		spot = SelectDeathmatchSpawnPoint ();
+	else if (coop->value)
+		spot = SelectCoopSpawnPoint (ent);
+
+	// find a single player start spot
+	if (!spot)
+	{
+		while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL)
+		{
+			if (!game.spawnpoint[0] && !spot->targetname)
+				break;
+
+			if (!game.spawnpoint[0] || !spot->targetname)
+				continue;
+
+			if (cistrcmp(game.spawnpoint, spot->targetname) == 0)
+				break;
+		}
+
+		if (!spot)
+		{
+			if (!game.spawnpoint[0])
+			{	// there wasn't a spawnpoint without a target, so use any
+				spot = G_Find (spot, FOFS(classname), "info_player_start");
+			}
+			if (!spot)
+				gi.error ("Couldn't find spawn point %s\n", game.spawnpoint);
+		}
+	}
+
+	VectorCopy (spot->s.origin, origin);
+	origin[2] += 9;
+	VectorCopy (spot->s.angles, angles);
+}
+
+//======================================================================
+
+
+void InitBodyQue (void)
+{
+	int		i;
+	edict_t	*ent;
+
+	level.body_que = 0;
+	for (i=0; i<BODY_QUEUE_SIZE ; i++)
+	{
+		ent = G_Spawn();
+		ent->classname = "bodyque";
+	}
+}
+
+void body_die (edict_t *self, edict_t *, edict_t *, int damage, vec3_t)
+{
+	int	n;
+
+	if (self->health < -40)
+	{
+		gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
+		for (n= 0; n < 4; n++)
+			ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
+		self->s.origin[2] -= 48;
+		ThrowClientHead (self, damage);
+		self->takedamage = DAMAGE_NO;
+	}
+}
+
+void CopyToBodyQue (edict_t *ent)
+{
+	edict_t		*body;
+
+	// grab a body que and cycle to the next one
+	body = &g_edicts[(int)maxclients->value + level.body_que + 1];
+	level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE;
+
+	// FIXME: send an effect on the removed body
+
+	gi.unlinkentity (ent);
+
+	gi.unlinkentity (body);
+	body->s = ent->s;
+	body->s.number = body - g_edicts;
+
+	body->svflags = ent->svflags;
+	VectorCopy (ent->mins, body->mins);
+	VectorCopy (ent->maxs, body->maxs);
+	VectorCopy (ent->absmin, body->absmin);
+	VectorCopy (ent->absmax, body->absmax);
+	VectorCopy (ent->size, body->size);
+	body->solid = ent->solid;
+	body->clipmask = ent->clipmask;
+	body->owner = ent->owner;
+	body->movetype = ent->movetype;
+
+	body->die = body_die;
+	body->takedamage = DAMAGE_YES;
+
+	gi.linkentity (body);
+}
+
+
+void respawn (edict_t *self)
+{
+	if (deathmatch->value || coop->value)
+	{
+		// spectator's don't leave bodies
+		if (self->movetype != MOVETYPE_NOCLIP)
+			CopyToBodyQue (self);
+		self->svflags &= ~SVF_NOCLIENT;
+		PutClientInServer (self);
+
+		// add a teleportation effect
+		self->s.event = EV_PLAYER_TELEPORT;
+
+		// hold in place briefly
+		self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		self->client->ps.pmove.pm_time = 14;
+
+		self->client->respawn_time = level.time;
+
+		return;
+	}
+
+	// restart the entire server
+	gi.AddCommandString ("menu_loadgame\n");
+}
+
+/* 
+ * only called when pers.spectator changes
+ * note that resp.spectator should be the opposite of pers.spectator here
+ */
+void spectator_respawn (edict_t *ent)
+{
+	int i, numspec;
+
+	// if the user wants to become a spectator, make sure he doesn't
+	// exceed max_spectators
+
+	if (ent->client->pers.spectator) {
+		char *value = Info_ValueForKey (ent->client->pers.userinfo, "spectator");
+		if (*spectator_password->string && 
+			strcmp(spectator_password->string, "none") && 
+			strcmp(spectator_password->string, value)) {
+			gi.cprintf(ent, PRINT_HIGH, "Spectator password incorrect.\n");
+			ent->client->pers.spectator = false;
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 0\n");
+			gi.unicast(ent, true);
+			return;
+		}
+
+		// count spectators
+		for (i = 1, numspec = 0; i <= maxclients->value; i++)
+			if (g_edicts[i].inuse && g_edicts[i].client->pers.spectator)
+				numspec++;
+
+		if (numspec >= maxspectators->value) {
+			gi.cprintf(ent, PRINT_HIGH, "Server spectator limit is full.");
+			ent->client->pers.spectator = false;
+			// reset his spectator var
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 0\n");
+			gi.unicast(ent, true);
+			return;
+		}
+	} else {
+		// he was a spectator and wants to join the game
+		// he must have the right password
+		char *value = Info_ValueForKey (ent->client->pers.userinfo, "password");
+		if (*password->string && strcmp(password->string, "none") && 
+			strcmp(password->string, value)) {
+			gi.cprintf(ent, PRINT_HIGH, "Password incorrect.\n");
+			ent->client->pers.spectator = true;
+			gi.WriteByte (svc_stufftext);
+			gi.WriteString ("spectator 1\n");
+			gi.unicast(ent, true);
+			return;
+		}
+	}
+
+	// clear score on respawn
+	ent->client->pers.score = ent->client->resp.score = 0;
+
+	ent->svflags &= ~SVF_NOCLIENT;
+	PutClientInServer (ent);
+
+	// add a teleportation effect
+	if (!ent->client->pers.spectator)  {
+		// send effect
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_LOGIN);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		// hold in place briefly
+		ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
+		ent->client->ps.pmove.pm_time = 14;
+	}
+
+	ent->client->respawn_time = level.time;
+
+	if (ent->client->pers.spectator) 
+		gi.bprintf (PRINT_HIGH, "%s has moved to the sidelines\n", ent->client->pers.netname);
+	else
+		gi.bprintf (PRINT_HIGH, "%s joined the game\n", ent->client->pers.netname);
+}
+
+//==============================================================
+
+
+/*
+===========
+PutClientInServer
+
+Called when a player connects to a server or respawns in
+a deathmatch.
+============
+*/
+void PutClientInServer (edict_t *ent)
+{
+	vec3_t	mins = {-16, -16, -24};
+	vec3_t	maxs = {16, 16, 32};
+	int		index;
+	vec3_t	spawn_origin, spawn_angles;
+	gclient_t	*client;
+	int		i;
+	client_persistant_t	saved;
+	client_respawn_t	resp;
+
+	// find a spawn point
+	// do it before setting health back up, so farthest
+	// ranging doesn't count this client
+	SelectSpawnPoint (ent, spawn_origin, spawn_angles);
+
+	index = ent-g_edicts-1;
+	client = ent->client;
+
+	// deathmatch wipes most client data every spawn
+	if (deathmatch->value)
+	{
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		InitClientPersistant (client);
+		ClientUserinfoChanged (ent, userinfo);
+	}
+	else if (coop->value)
+	{
+//		int			n;
+		char		userinfo[MAX_INFO_STRING];
+
+		resp = client->resp;
+		memcpy (userinfo, client->pers.userinfo, sizeof(userinfo));
+		// this is kind of ugly, but it's how we want to handle keys in coop
+//		for (n = 0; n < game.num_items; n++)
+//		{
+//			if (itemlist[n].flags & IT_KEY)
+//				resp.coop_respawn.inventory[n] = client->pers.inventory[n];
+//		}
+		resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged;
+		resp.coop_respawn.helpchanged = client->pers.helpchanged;
+		client->pers = resp.coop_respawn;
+		ClientUserinfoChanged (ent, userinfo);
+		if (resp.score > client->pers.score)
+			client->pers.score = resp.score;
+	}
+	else
+	{
+		memset (&resp, 0, sizeof(resp));
+	}
+
+	// clear everything but the persistant data
+	saved = client->pers;
+	memset (client, 0, sizeof(*client));
+	client->pers = saved;
+	if (client->pers.health <= 0)
+		InitClientPersistant(client);
+	client->resp = resp;
+
+	// copy some data from the client to the entity
+	FetchClientEntData (ent);
+
+	// clear entity values
+	ent->groundentity = NULL;
+	ent->client = &game.clients[index];
+	ent->takedamage = DAMAGE_AIM;
+	ent->movetype = MOVETYPE_WALK;
+	ent->viewheight = 22;
+	ent->inuse = true;
+	ent->classname = "player";
+	ent->mass = 200;
+	ent->solid = SOLID_BBOX;
+	ent->deadflag = DEAD_NO;
+	ent->air_finished = level.time + 12;
+	ent->clipmask = MASK_PLAYERSOLID;
+	ent->model = "players/male/tris.md2";
+	ent->pain = player_pain;
+	ent->die = player_die;
+	ent->waterlevel = 0;
+	ent->watertype = 0;
+	ent->flags &= ~FL_NO_KNOCKBACK;
+	ent->svflags &= ~SVF_DEADMONSTER;
+
+	VectorCopy (mins, ent->mins);
+	VectorCopy (maxs, ent->maxs);
+	VectorClear (ent->velocity);
+
+	// clear playerstate values
+	memset (&ent->client->ps, 0, sizeof(client->ps));
+
+	client->ps.pmove.origin[0] = spawn_origin[0]*8;
+	client->ps.pmove.origin[1] = spawn_origin[1]*8;
+	client->ps.pmove.origin[2] = spawn_origin[2]*8;
+
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		client->ps.fov = 90;
+	}
+	else
+	{
+		client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov"));
+		if (client->ps.fov < 1)
+			client->ps.fov = 90;
+		else if (client->ps.fov > 160)
+			client->ps.fov = 160;
+	}
+
+	client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model);
+
+	// clear entity state values
+	ent->s.effects = 0;
+	ent->s.skinnum = ent - g_edicts - 1;
+	ent->s.modelindex = 255;		// will use the skin specified model
+	ent->s.modelindex2 = 255;		// custom gun model
+	ent->s.frame = 0;
+	VectorCopy (spawn_origin, ent->s.origin);
+	ent->s.origin[2] += 1;	// make sure off ground
+	VectorCopy (ent->s.origin, ent->s.old_origin);
+
+	// set the delta angle
+	for (i=0 ; i<3 ; i++)
+		client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]);
+
+	ent->s.angles[PITCH] = 0;
+	ent->s.angles[YAW] = spawn_angles[YAW];
+	ent->s.angles[ROLL] = 0;
+	VectorCopy (ent->s.angles, client->ps.viewangles);
+	VectorCopy (ent->s.angles, client->v_angle);
+
+	// spawn a spectator
+	if (client->pers.spectator) {
+		client->chase_target = NULL;
+
+		client->resp.spectator = true;
+
+
+		ent->movetype = MOVETYPE_NOCLIP;
+		ent->solid = SOLID_NOT;
+		ent->svflags |= SVF_NOCLIENT;
+		ent->client->ps.gunindex = 0;
+		gi.linkentity (ent);
+		return;
+	} else
+
+		client->resp.spectator = false;
+
+
+	if (!KillBox (ent))
+	{	// could't spawn in?
+	}
+
+	gi.linkentity (ent);
+
+	// force the current weapon up
+	client->newweapon = client->pers.weapon;
+	ChangeWeapon (ent);
+}
+
+/*
+=====================
+ClientBeginDeathmatch
+
+A client has just connected to the server in 
+deathmatch mode, so clear everything out before starting them.
+=====================
+*/
+void ClientBeginDeathmatch (edict_t *ent)
+{
+	G_InitEdict (ent);
+
+	InitClientResp (ent->client);
+
+	// locate ent at a spawn point
+	PutClientInServer (ent);
+
+	if (level.intermissiontime)
+	{
+		MoveClientToIntermission (ent);
+	}
+	else
+	{
+		// send effect
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_LOGIN);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+	}
+
+	gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+
+/*
+===========
+ClientBegin
+
+called when a client has finished connecting, and is ready
+to be placed into the game.  This will happen every level load.
+============
+*/
+void ClientBegin (edict_t *ent)
+{
+	int		i;
+
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	if (deathmatch->value)
+	{
+		ClientBeginDeathmatch (ent);
+		return;
+	}
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == true)
+	{
+		// the client has cleared the client side viewangles upon
+		// connecting to the server, which is different than the
+		// state when the game is saved, so we need to compensate
+		// with deltaangles
+		for (i=0 ; i<3 ; i++)
+			ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]);
+	}
+	else
+	{
+		// a spawn point will completely reinitialize the entity
+		// except for the persistant data that was initialized at
+		// ClientConnect() time
+		G_InitEdict (ent);
+		ent->classname = "player";
+		InitClientResp (ent->client);
+		PutClientInServer (ent);
+	}
+
+	if (level.intermissiontime)
+	{
+		MoveClientToIntermission (ent);
+	}
+	else
+	{
+		// send effect if in a multiplayer game
+		if (game.maxclients > 1)
+		{
+			gi.WriteByte (svc_muzzleflash);
+			gi.WriteShort (ent-g_edicts);
+			gi.WriteByte (MZ_LOGIN);
+			gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+			gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
+		}
+	}
+
+	// make sure all view stuff is valid
+	ClientEndServerFrame (ent);
+}
+
+/*
+===========
+ClientUserInfoChanged
+
+called whenever the player updates a userinfo variable.
+
+The game can override any of the settings in place
+(forcing skins or names, etc) before copying it off.
+============
+*/
+void ClientUserinfoChanged (edict_t *ent, char *userinfo)
+{
+	char	*s;
+	int		playernum;
+
+	// check for malformed or illegal info strings
+	if (!Info_Validate(userinfo))
+	{
+		strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt");
+	}
+
+	// set name
+	s = Info_ValueForKey (userinfo, "name");
+	strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1);
+
+	// set spectator
+	s = Info_ValueForKey (userinfo, "spectator");
+	// spectators are only supported in deathmatch
+
+	if (deathmatch->value && *s && strcmp(s, "0"))
+		ent->client->pers.spectator = true;
+	else
+		ent->client->pers.spectator = false;
+
+	// set skin
+	s = Info_ValueForKey (userinfo, "skin");
+
+	playernum = ent-g_edicts-1;
+
+	// combine name and skin into a configstring
+	gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) );
+
+	// fov
+	if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV))
+	{
+		ent->client->ps.fov = 90;
+	}
+	else
+	{
+		ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov"));
+		if (ent->client->ps.fov < 1)
+			ent->client->ps.fov = 90;
+		else if (ent->client->ps.fov > 160)
+			ent->client->ps.fov = 160;
+	}
+
+	// handedness
+	s = Info_ValueForKey (userinfo, "hand");
+	if (strlen(s))
+	{
+		ent->client->pers.hand = atoi(s);
+	}
+
+	// save off the userinfo in case we want to check something later
+	strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1);
+}
+
+
+/*
+===========
+ClientConnect
+
+Called when a player begins connecting to the server.
+The game can refuse entrance to a client by returning false.
+If the client is allowed, the connection process will continue
+and eventually get to ClientBegin()
+Changing levels will NOT cause this to be called again, but
+loadgames will.
+============
+*/
+qboolean ClientConnect (edict_t *ent, char *userinfo)
+{
+	char	*value;
+
+	// check to see if they are on the banned IP list
+	value = Info_ValueForKey (userinfo, "ip");
+	if (SV_FilterPacket(value)) {
+		Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
+		return false;
+	}
+
+	// check for a spectator
+	value = Info_ValueForKey (userinfo, "spectator");
+	if (deathmatch->value && *value && strcmp(value, "0")) {
+		int i, numspec;
+
+		if (*spectator_password->string && 
+			strcmp(spectator_password->string, "none") && 
+			strcmp(spectator_password->string, value)) {
+			Info_SetValueForKey(userinfo, "rejmsg", "Spectator password required or incorrect.");
+			return false;
+		}
+
+		// count spectators
+		for (i = numspec = 0; i < maxclients->value; i++)
+			if (g_edicts[i+1].inuse && g_edicts[i+1].client->pers.spectator)
+				numspec++;
+
+		if (numspec >= maxspectators->value) {
+			Info_SetValueForKey(userinfo, "rejmsg", "Server spectator limit is full.");
+			return false;
+		}
+	} else {
+		// check for a password
+		value = Info_ValueForKey (userinfo, "password");
+		if (*password->string && strcmp(password->string, "none") && 
+			strcmp(password->string, value)) {
+			Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect.");
+			return false;
+		}
+	}
+
+	// they can connect
+	ent->client = game.clients + (ent - g_edicts - 1);
+
+	// if there is already a body waiting for us (a loadgame), just
+	// take it, otherwise spawn one from scratch
+	if (ent->inuse == false)
+	{
+		// clear the respawning variables
+		InitClientResp (ent->client);
+		if (!game.autosaved || !ent->client->pers.weapon)
+			InitClientPersistant (ent->client);
+	}
+
+
+	ClientUserinfoChanged (ent, userinfo);
+
+	if (game.maxclients > 1)
+		gi.dprintf ("%s connected\n", ent->client->pers.netname);
+
+	ent->svflags = 0; // make sure we start with known default
+	ent->client->pers.connected = true;
+	return true;
+}
+
+/*
+===========
+ClientDisconnect
+
+Called when a player drops from the server.
+Will not be called between levels.
+============
+*/
+void ClientDisconnect (edict_t *ent)
+{
+	int		playernum;
+
+	if (!ent->client)
+		return;
+
+	gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);
+
+	// send effect
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_LOGOUT);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	gi.unlinkentity (ent);
+	ent->s.modelindex = 0;
+	ent->solid = SOLID_NOT;
+	ent->inuse = false;
+	ent->classname = "disconnected";
+	ent->client->pers.connected = false;
+
+	playernum = ent-g_edicts-1;
+	gi.configstring (CS_PLAYERSKINS+playernum, "");
+}
+
+
+//==============================================================
+
+
+edict_t	*pm_passent;
+
+// pmove doesn't need to know about passent and contentmask
+trace_t	PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
+{
+	if (pm_passent->health > 0)
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID);
+	else
+		return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID);
+}
+
+unsigned CheckBlock (void *b, int c)
+{
+	int	v,i;
+	v = 0;
+	for (i=0 ; i<c ; i++)
+		v+= ((byte *)b)[i];
+	return v;
+}
+void PrintPmove (pmove_t *pm)
+{
+	unsigned	c1, c2;
+
+	c1 = CheckBlock (&pm->s, sizeof(pm->s));
+	c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd));
+	Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2);
+}
+
+/*
+==============
+ClientThink
+
+This will be called once for each client frame, which will
+usually be a couple times for each server frame.
+==============
+*/
+void ClientThink (edict_t *ent, usercmd_t *ucmd)
+{
+	gclient_t	*client;
+	edict_t	*other;
+	int		i, j;
+	pmove_t	pm;
+
+	level.current_entity = ent;
+	client = ent->client;
+
+	if (level.intermissiontime)
+	{
+		client->ps.pmove.pm_type = PM_FREEZE;
+		// can exit intermission after five seconds
+		if (level.time > level.intermissiontime + 5.0 
+			&& (ucmd->buttons & BUTTON_ANY) )
+			level.exitintermission = true;
+		return;
+	}
+
+	pm_passent = ent;
+
+	if (ent->client->chase_target) {
+
+		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+	} else {
+
+		// set up for pmove
+		memset (&pm, 0, sizeof(pm));
+
+		if (ent->movetype == MOVETYPE_NOCLIP)
+			client->ps.pmove.pm_type = PM_SPECTATOR;
+		else if (ent->s.modelindex != 255)
+			client->ps.pmove.pm_type = PM_GIB;
+		else if (ent->deadflag)
+			client->ps.pmove.pm_type = PM_DEAD;
+		else
+			client->ps.pmove.pm_type = PM_NORMAL;
+
+		client->ps.pmove.gravity = sv_gravity->value;
+		pm.s = client->ps.pmove;
+
+		for (i=0 ; i<3 ; i++)
+		{
+			pm.s.origin[i] = ent->s.origin[i]*8;
+			pm.s.velocity[i] = ent->velocity[i]*8;
+		}
+
+		if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s)))
+		{
+			pm.snapinitial = true;
+	//		gi.dprintf ("pmove changed!\n");
+		}
+
+		pm.cmd = *ucmd;
+
+		pm.trace = PM_trace;	// adds default parms
+		pm.pointcontents = gi.pointcontents;
+
+		// perform a pmove
+		gi.Pmove (&pm);
+
+		// save results of pmove
+		client->ps.pmove = pm.s;
+		client->old_pmove = pm.s;
+
+		for (i=0 ; i<3 ; i++)
+		{
+			ent->s.origin[i] = pm.s.origin[i]*0.125;
+			ent->velocity[i] = pm.s.velocity[i]*0.125;
+		}
+
+		VectorCopy (pm.mins, ent->mins);
+		VectorCopy (pm.maxs, ent->maxs);
+
+		client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
+		client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
+		client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
+
+		if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
+			PlayerNoise(ent, ent->s.origin, PNOISE_SELF);
+		}
+
+		ent->viewheight = pm.viewheight;
+		ent->waterlevel = pm.waterlevel;
+		ent->watertype = pm.watertype;
+		ent->groundentity = pm.groundentity;
+		if (pm.groundentity)
+			ent->groundentity_linkcount = pm.groundentity->linkcount;
+
+		if (ent->deadflag)
+		{
+			client->ps.viewangles[ROLL] = 40;
+			client->ps.viewangles[PITCH] = -15;
+			client->ps.viewangles[YAW] = client->killer_yaw;
+		}
+		else
+		{
+			VectorCopy (pm.viewangles, client->v_angle);
+			VectorCopy (pm.viewangles, client->ps.viewangles);
+		}
+
+		gi.linkentity (ent);
+
+		if (ent->movetype != MOVETYPE_NOCLIP)
+			G_TouchTriggers (ent);
+
+		// touch other objects
+		for (i=0 ; i<pm.numtouch ; i++)
+		{
+			other = pm.touchents[i];
+			for (j=0 ; j<i ; j++)
+				if (pm.touchents[j] == other)
+					break;
+			if (j != i)
+				continue;	// duplicated
+			if (!other->touch)
+				continue;
+			other->touch (other, ent, NULL, NULL);
+		}
+	}
+
+
+	client->oldbuttons = client->buttons;
+	client->buttons = ucmd->buttons;
+	client->latched_buttons |= client->buttons & ~client->oldbuttons;
+
+	// save light level the player is standing on for
+	// monster sighting AI
+	ent->light_level = ucmd->lightlevel;
+
+	// fire weapon from final position if needed
+	if (client->latched_buttons & BUTTON_ATTACK)
+	{
+		if (client->resp.spectator) {
+
+			client->latched_buttons = 0;
+
+			if (client->chase_target) {
+				client->chase_target = NULL;
+				client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
+			} else
+				GetChaseTarget(ent);
+
+		} else if (!client->weapon_thunk) {
+			client->weapon_thunk = true;
+			Think_Weapon (ent);
+		}
+	}
+
+	if (client->resp.spectator) {
+		if (ucmd->upmove >= 10) {
+			if (!(client->ps.pmove.pm_flags & PMF_JUMP_HELD)) {
+				client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
+				if (client->chase_target)
+					ChaseNext(ent);
+				else
+					GetChaseTarget(ent);
+			}
+		} else
+			client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;
+	}
+
+	// update chase cam if being followed
+	for (i = 1; i <= maxclients->value; i++) {
+		other = g_edicts + i;
+		if (other->inuse && other->client->chase_target == ent)
+			UpdateChaseCam(other);
+	}
+}
+
+
+/*
+==============
+ClientBeginServerFrame
+
+This will be called once for each server frame, before running
+any other entities in the world.
+==============
+*/
+void ClientBeginServerFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	int			buttonMask;
+
+	if (level.intermissiontime)
+		return;
+
+	client = ent->client;
+
+	if (deathmatch->value &&
+
+		client->pers.spectator != client->resp.spectator &&
+
+		(level.time - client->respawn_time) >= 5) {
+
+		spectator_respawn(ent);
+
+		return;
+
+	}
+
+
+
+	// run weapon animations if it hasn't been done by a ucmd_t
+	if (!client->weapon_thunk && !client->resp.spectator)
+
+		Think_Weapon (ent);
+	else
+		client->weapon_thunk = false;
+
+	if (ent->deadflag)
+	{
+		// wait for any button just going down
+		if ( level.time > client->respawn_time)
+		{
+			// in deathmatch, only wait for attack button
+			if (deathmatch->value)
+				buttonMask = BUTTON_ATTACK;
+			else
+				buttonMask = -1;
+
+			if ( ( client->latched_buttons & buttonMask ) ||
+				(deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) )
+			{
+				respawn(ent);
+				client->latched_buttons = 0;
+			}
+		}
+		return;
+	}
+
+	// add player trail so monsters can follow
+	if (!deathmatch->value)
+		if (!visible (ent, PlayerTrail_LastSpot() ) )
+			PlayerTrail_Add (ent->s.old_origin);
+
+	client->latched_buttons = 0;
+}
--- /dev/null
+++ b/xatrix/p_hud.c
@@ -1,0 +1,571 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+
+/*
+======================================================================
+
+INTERMISSION
+
+======================================================================
+*/
+
+void MoveClientToIntermission (edict_t *ent)
+{
+	if (deathmatch->value || coop->value)
+		ent->client->showscores = true;
+	VectorCopy (level.intermission_origin, ent->s.origin);
+	ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
+	ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
+	ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
+	VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
+	ent->client->ps.pmove.pm_type = PM_FREEZE;
+	ent->client->ps.gunindex = 0;
+	ent->client->ps.blend[3] = 0;
+	ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	// clean up powerup info
+	ent->client->quad_framenum = 0;
+	ent->client->invincible_framenum = 0;
+	ent->client->breather_framenum = 0;
+	ent->client->enviro_framenum = 0;
+	ent->client->grenade_blew_up = false;
+	ent->client->grenade_time = 0;
+
+	// RAFAEL
+	ent->client->quadfire_framenum = 0;
+	
+	// RAFAEL
+	ent->client->trap_blew_up = false;
+	ent->client->trap_time = 0;
+	
+	ent->viewheight = 0;
+	ent->s.modelindex = 0;
+	ent->s.modelindex2 = 0;
+	ent->s.modelindex3 = 0;
+	ent->s.modelindex = 0;
+	ent->s.effects = 0;
+	ent->s.sound = 0;
+	ent->solid = SOLID_NOT;
+
+	gi.linkentity(ent);
+
+	// add the layout
+
+	if (deathmatch->value || coop->value)
+	{
+		DeathmatchScoreboardMessage (ent, NULL);
+		gi.unicast (ent, true);
+	}
+
+}
+
+void BeginIntermission (edict_t *targ)
+{
+	int		i, n;
+	edict_t	*ent, *client;
+
+	if (level.intermissiontime)
+		return;		// already activated
+
+	game.autosaved = false;
+
+	// respawn any dead clients
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		if (client->health <= 0)
+			respawn(client);
+	}
+
+	level.intermissiontime = level.time;
+	level.changemap = targ->map;
+
+	if (strstr(level.changemap, "*"))
+	{
+		if (coop->value)
+		{
+			for (i=0 ; i<maxclients->value ; i++)
+			{
+				client = g_edicts + 1 + i;
+				if (!client->inuse)
+					continue;
+				// strip players of all keys between units
+				for (n = 0; n < MAX_ITEMS; n++)
+				{
+					if (itemlist[n].flags & IT_KEY)
+						client->client->pers.inventory[n] = 0;
+				}
+			}
+		}
+	}
+	else
+	{
+		if (!deathmatch->value)
+		{
+			level.exitintermission = 1;		// go immediately to the next level
+			return;
+		}
+	}
+
+	level.exitintermission = 0;
+
+	// find an intermission spot
+	ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
+	if (!ent)
+	{	// the map creator forgot to put in an intermission point...
+		ent = G_Find (NULL, FOFS(classname), "info_player_start");
+		if (!ent)
+			ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
+	}
+	else
+	{	// chose one of four spots
+		i = rand() & 3;
+		while (i--)
+		{
+			ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+			if (!ent)	// wrap around the list
+				ent = G_Find (ent, FOFS(classname), "info_player_intermission");
+		}
+	}
+
+	VectorCopy (ent->s.origin, level.intermission_origin);
+	VectorCopy (ent->s.angles, level.intermission_angle);
+
+	// move all clients to the intermission point
+	for (i=0 ; i<maxclients->value ; i++)
+	{
+		client = g_edicts + 1 + i;
+		if (!client->inuse)
+			continue;
+		MoveClientToIntermission (client);
+	}
+}
+
+
+/*
+==================
+DeathmatchScoreboardMessage
+
+==================
+*/
+void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
+{
+	char	entry[1024];
+	char	string[1400];
+	int		stringlength;
+	int		i, j, k;
+	int		sorted[MAX_CLIENTS];
+	int		sortedscores[MAX_CLIENTS];
+	int		score, total;
+	int		x, y;
+	gclient_t	*cl;
+	edict_t		*cl_ent;
+	char	*tag;
+
+	// sort the clients by score
+	total = 0;
+	for (i=0 ; i<game.maxclients ; i++)
+	{
+		cl_ent = g_edicts + 1 + i;
+		if (!cl_ent->inuse || game.clients[i].resp.spectator)
+			continue;
+		score = game.clients[i].resp.score;
+		for (j=0 ; j<total ; j++)
+		{
+			if (score > sortedscores[j])
+				break;
+		}
+		for (k=total ; k>j ; k--)
+		{
+			sorted[k] = sorted[k-1];
+			sortedscores[k] = sortedscores[k-1];
+		}
+		sorted[j] = i;
+		sortedscores[j] = score;
+		total++;
+	}
+
+	// print level name and exit rules
+	string[0] = 0;
+
+	stringlength = strlen(string);
+
+	// add the clients in sorted order
+	if (total > 12)
+		total = 12;
+
+	for (i=0 ; i<total ; i++)
+	{
+		cl = &game.clients[sorted[i]];
+		cl_ent = g_edicts + 1 + sorted[i];
+
+		x = (i>=6) ? 160 : 0;
+		y = 32 + 32 * (i%6);
+
+		// add a dogtag
+		if (cl_ent == ent)
+			tag = "tag1";
+		else if (cl_ent == killer)
+			tag = "tag2";
+		else
+			tag = NULL;
+		if (tag)
+		{
+			Com_sprintf (entry, sizeof(entry),
+				"xv %i yv %i picn %s ",x+32, y, tag);
+			j = strlen(entry);
+			if (stringlength + j > 1024)
+				break;
+			strcpy (string + stringlength, entry);
+			stringlength += j;
+		}
+
+		// send the layout
+		Com_sprintf (entry, sizeof(entry),
+			"client %i %i %i %i %i %i ",
+			x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
+		j = strlen(entry);
+		if (stringlength + j > 1024)
+			break;
+		strcpy (string + stringlength, entry);
+		stringlength += j;
+	}
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+}
+
+
+/*
+==================
+DeathmatchScoreboard
+
+Draw instead of help message.
+Note that it isn't that hard to overflow the 1400 byte message limit!
+==================
+*/
+void DeathmatchScoreboard (edict_t *ent)
+{
+	DeathmatchScoreboardMessage (ent, ent->enemy);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Score_f
+
+Display the scoreboard
+==================
+*/
+void Cmd_Score_f (edict_t *ent)
+{
+	ent->client->showinventory = false;
+	ent->client->showhelp = false;
+
+	if (!deathmatch->value && !coop->value)
+		return;
+
+	if (ent->client->showscores)
+	{
+		ent->client->showscores = false;
+		return;
+	}
+
+	ent->client->showscores = true;
+	DeathmatchScoreboard (ent);
+}
+
+
+/*
+==================
+HelpComputer
+
+Draw help computer.
+==================
+*/
+void HelpComputer (edict_t *ent)
+{
+	char	string[1024];
+	char	*sk;
+
+	if (skill->value == 0)
+		sk = "easy";
+	else if (skill->value == 1)
+		sk = "medium";
+	else if (skill->value == 2)
+		sk = "hard";
+	else
+		sk = "hard+";
+
+	// send the layout
+	Com_sprintf (string, sizeof(string),
+		"xv 32 yv 8 picn help "			// background
+		"xv 202 yv 12 string2 \"%s\" "		// skill
+		"xv 0 yv 24 cstring2 \"%s\" "		// level name
+		"xv 0 yv 54 cstring2 \"%s\" "		// help 1
+		"xv 0 yv 110 cstring2 \"%s\" "		// help 2
+		"xv 50 yv 164 string2 \" kills     goals    secrets\" "
+		"xv 50 yv 172 string2 \"%3i/%3i     %i/%i       %i/%i\" ", 
+		sk,
+		level.level_name,
+		game.helpmessage1,
+		game.helpmessage2,
+		level.killed_monsters, level.total_monsters, 
+		level.found_goals, level.total_goals,
+		level.found_secrets, level.total_secrets);
+
+	gi.WriteByte (svc_layout);
+	gi.WriteString (string);
+	gi.unicast (ent, true);
+}
+
+
+/*
+==================
+Cmd_Help_f
+
+Display the current help message
+==================
+*/
+void Cmd_Help_f (edict_t *ent)
+{
+	// this is for backwards compatability
+	if (deathmatch->value)
+	{
+		Cmd_Score_f (ent);
+		return;
+	}
+
+	ent->client->showinventory = false;
+	ent->client->showscores = false;
+
+	if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged))
+	{
+		ent->client->showhelp = false;
+		return;
+	}
+
+	ent->client->showhelp = true;
+	ent->client->pers.helpchanged = 0;
+	HelpComputer (ent);
+}
+
+
+//=======================================================================
+
+/*
+===============
+G_SetStats
+===============
+*/
+void G_SetStats (edict_t *ent)
+{
+	gitem_t		*item;
+	int			index, cells = 0;
+	int			power_armor_type;
+
+	//
+	// health
+	//
+	ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
+	ent->client->ps.stats[STAT_HEALTH] = ent->health;
+
+	//
+	// ammo
+	//
+	if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
+	{
+		ent->client->ps.stats[STAT_AMMO_ICON] = 0;
+		ent->client->ps.stats[STAT_AMMO] = 0;
+	}
+	else
+	{
+		item = &itemlist[ent->client->ammo_index];
+		ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
+	}
+	
+	//
+	// armor
+	//
+	power_armor_type = PowerArmorType (ent);
+	if (power_armor_type)
+	{
+		cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
+		if (cells == 0)
+		{	// ran out of cells for power armor
+			ent->flags &= ~FL_POWER_ARMOR;
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
+			power_armor_type = 0;;
+		}
+	}
+
+	index = ArmorIndex (ent);
+	if (power_armor_type && (!index || (level.framenum & 8) ) )
+	{	// flash between power armor and other armor icon
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
+		ent->client->ps.stats[STAT_ARMOR] = cells;
+	}
+	else if (index)
+	{
+		item = GetItemByIndex (index);
+		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
+		ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
+	}
+	else
+	{
+		ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
+		ent->client->ps.stats[STAT_ARMOR] = 0;
+	}
+
+	//
+	// pickup message
+	//
+	if (level.time > ent->client->pickup_msg_time)
+	{
+		ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
+		ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
+	}
+
+	//
+	// timers
+	//
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
+	}
+	// RAFAEL
+	else if (ent->client->quadfire_framenum > level.framenum)
+	{
+		// note to self
+		// need to change imageindex
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quadfire");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->quadfire_framenum - level.framenum)/10;
+	}
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
+		ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
+	}
+	else
+	{
+		ent->client->ps.stats[STAT_TIMER_ICON] = 0;
+		ent->client->ps.stats[STAT_TIMER] = 0;
+	}
+
+	//
+	// selected item
+	//
+	if (ent->client->pers.selected_item == -1)
+		ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
+	else
+		ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
+
+	ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
+
+	//
+	// layouts
+	//
+	ent->client->ps.stats[STAT_LAYOUTS] = 0;
+
+	if (deathmatch->value)
+	{
+		if (ent->client->pers.health <= 0 || level.intermissiontime
+			|| ent->client->showscores)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+	else
+	{
+		if (ent->client->showscores || ent->client->showhelp)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
+		if (ent->client->showinventory && ent->client->pers.health > 0)
+			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
+	}
+
+	//
+	// frags
+	//
+	ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
+
+	//
+	// help icon / current weapon if not shown
+	//
+	if (ent->client->pers.helpchanged && (level.framenum&8) )
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
+	else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
+		&& ent->client->pers.weapon)
+		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
+	else
+		ent->client->ps.stats[STAT_HELPICON] = 0;
+
+	ent->client->ps.stats[STAT_SPECTATOR] = 0;
+}
+
+/*
+===============
+G_CheckChaseStats
+===============
+*/
+void G_CheckChaseStats (edict_t *ent)
+{
+	int i;
+	gclient_t *cl;
+
+	for (i = 1; i <= maxclients->value; i++) {
+		cl = g_edicts[i].client;
+		if (!g_edicts[i].inuse || cl->chase_target != ent)
+			continue;
+		memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
+		G_SetSpectatorStats(g_edicts + i);
+	}
+}
+
+/*
+===============
+G_SetSpectatorStats
+===============
+*/
+void G_SetSpectatorStats (edict_t *ent)
+{
+	gclient_t *cl = ent->client;
+
+	if (!cl->chase_target)
+		G_SetStats (ent);
+
+	cl->ps.stats[STAT_SPECTATOR] = 1;
+
+	// layouts are independant in spectator
+	cl->ps.stats[STAT_LAYOUTS] = 0;
+	if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
+		cl->ps.stats[STAT_LAYOUTS] |= 1;
+	if (cl->showinventory && cl->pers.health > 0)
+		cl->ps.stats[STAT_LAYOUTS] |= 2;
+
+	if (cl->chase_target && cl->chase_target->inuse)
+		cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS + 
+			(cl->chase_target - g_edicts) - 1;
+	else
+		cl->ps.stats[STAT_CHASE] = 0;
+}
+
--- /dev/null
+++ b/xatrix/p_trail.c
@@ -1,0 +1,131 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+
+/*
+==============================================================================
+
+PLAYER TRAIL
+
+==============================================================================
+
+This is a circular list containing the a list of points of where
+the player has been recently.  It is used by monsters for pursuit.
+
+.origin		the spot
+.owner		forward link
+.aiment		backward link
+*/
+
+
+#define	TRAIL_LENGTH	8
+
+edict_t		*trail[TRAIL_LENGTH];
+int			trail_head;
+qboolean	trail_active = false;
+
+#define NEXT(n)		(((n) + 1) & (TRAIL_LENGTH - 1))
+#define PREV(n)		(((n) - 1) & (TRAIL_LENGTH - 1))
+
+
+void PlayerTrail_Init (void)
+{
+	int		n;
+
+	if (deathmatch->value /* FIXME || coop */)
+		return;
+
+	for (n = 0; n < TRAIL_LENGTH; n++)
+	{
+		trail[n] = G_Spawn();
+		trail[n]->classname = "player_trail";
+	}
+
+	trail_head = 0;
+	trail_active = true;
+}
+
+
+void PlayerTrail_Add (vec3_t spot)
+{
+	vec3_t	temp;
+
+	if (!trail_active)
+		return;
+
+	VectorCopy (spot, trail[trail_head]->s.origin);
+
+	trail[trail_head]->timestamp = level.time;
+
+	VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp);
+	trail[trail_head]->s.angles[1] = vectoyaw (temp);
+
+	trail_head = NEXT(trail_head);
+}
+
+
+void PlayerTrail_New (vec3_t spot)
+{
+	if (!trail_active)
+		return;
+
+	PlayerTrail_Init ();
+	PlayerTrail_Add (spot);
+}
+
+
+edict_t *PlayerTrail_PickFirst (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	if (visible(self, trail[marker]))
+	{
+		return trail[marker];
+	}
+
+	if (visible(self, trail[PREV(marker)]))
+	{
+		return trail[PREV(marker)];
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_PickNext (edict_t *self)
+{
+	int		marker;
+	int		n;
+
+	if (!trail_active)
+		return NULL;
+
+	for (marker = trail_head, n = TRAIL_LENGTH; n; n--)
+	{
+		if(trail[marker]->timestamp <= self->monsterinfo.trail_time)
+			marker = NEXT(marker);
+		else
+			break;
+	}
+
+	return trail[marker];
+}
+
+edict_t *PlayerTrail_LastSpot (void)
+{
+	return trail[PREV(trail_head)];
+}
--- /dev/null
+++ b/xatrix/p_view.c
@@ -1,0 +1,1092 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+
+static	edict_t		*current_player;
+static	gclient_t	*current_client;
+
+static	vec3_t	forward, right, up;
+float	xyspeed;
+
+float	bobmove;
+int		bobcycle;		// odd cycles are right foot going forward
+float	bobfracsin;		// sin(bobfrac*M_PI)
+
+/*
+===============
+SV_CalcRoll
+
+===============
+*/
+float SV_CalcRoll (vec3_t, vec3_t velocity)
+{
+	float	sign;
+	float	side;
+	float	value;
+	
+	side = DotProduct (velocity, right);
+	sign = side < 0 ? -1 : 1;
+	side = fabs(side);
+	
+	value = sv_rollangle->value;
+
+	if (side < sv_rollspeed->value)
+		side = side * value / sv_rollspeed->value;
+	else
+		side = value;
+	
+	return side*sign;
+	
+}
+
+
+/*
+===============
+P_DamageFeedback
+
+Handles color blends and view kicks
+===============
+*/
+void P_DamageFeedback (edict_t *player)
+{
+	gclient_t	*client;
+	float	side;
+	float	realcount, count, kick;
+	vec3_t	v;
+	int		r, l;
+	static	vec3_t	power_color = {0.0, 1.0, 0.0};
+	static	vec3_t	acolor = {1.0, 1.0, 1.0};
+	static	vec3_t	bcolor = {1.0, 0.0, 0.0};
+
+	client = player->client;
+
+	// flash the backgrounds behind the status numbers
+	client->ps.stats[STAT_FLASHES] = 0;
+	if (client->damage_blood)
+		client->ps.stats[STAT_FLASHES] |= 1;
+	if (client->damage_armor && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+		client->ps.stats[STAT_FLASHES] |= 2;
+
+	// total points of damage shot at the player this frame
+	count = (client->damage_blood + client->damage_armor + client->damage_parmor);
+	if (count == 0)
+		return;		// didn't take any damage
+
+	// start a pain animation if still in the player model
+	if (client->anim_priority < ANIM_PAIN && player->s.modelindex == 255)
+	{
+		static int		i;
+
+		client->anim_priority = ANIM_PAIN;
+		if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		{
+			player->s.frame = FRAME_crpain1-1;
+			client->anim_end = FRAME_crpain4;
+		}
+		else
+		{
+			i = (i+1)%3;
+			switch (i)
+			{
+			case 0:
+				player->s.frame = FRAME_pain101-1;
+				client->anim_end = FRAME_pain104;
+				break;
+			case 1:
+				player->s.frame = FRAME_pain201-1;
+				client->anim_end = FRAME_pain204;
+				break;
+			case 2:
+				player->s.frame = FRAME_pain301-1;
+				client->anim_end = FRAME_pain304;
+				break;
+			}
+		}
+	}
+
+	realcount = count;
+	if (count < 10)
+		count = 10;	// always make a visible effect
+
+	// play an apropriate pain sound
+	if ((level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && (client->invincible_framenum <= level.framenum))
+	{
+		r = 1 + (rand()&1);
+		player->pain_debounce_time = level.time + 0.7;
+		if (player->health < 25)
+			l = 25;
+		else if (player->health < 50)
+			l = 50;
+		else if (player->health < 75)
+			l = 75;
+		else
+			l = 100;
+		gi.sound (player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", l, r)), 1, ATTN_NORM, 0);
+	}
+
+	// the total alpha of the blend is always proportional to count
+	if (client->damage_alpha < 0)
+		client->damage_alpha = 0;
+	client->damage_alpha += count*0.01;
+	if (client->damage_alpha < 0.2)
+		client->damage_alpha = 0.2;
+	if (client->damage_alpha > 0.6)
+		client->damage_alpha = 0.6;		// don't go too saturated
+
+	// the color of the blend will vary based on how much was absorbed
+	// by different armors
+	VectorClear (v);
+	if (client->damage_parmor)
+		VectorMA (v, (float)client->damage_parmor/realcount, power_color, v);
+	if (client->damage_armor)
+		VectorMA (v, (float)client->damage_armor/realcount,  acolor, v);
+	if (client->damage_blood)
+		VectorMA (v, (float)client->damage_blood/realcount,  bcolor, v);
+	VectorCopy (v, client->damage_blend);
+
+
+	//
+	// calculate view angle kicks
+	//
+	kick = abs(client->damage_knockback);
+	if (kick && player->health > 0)	// kick of 0 means no view adjust at all
+	{
+		kick = kick * 100 / player->health;
+
+		if (kick < count*0.5)
+			kick = count*0.5;
+		if (kick > 50)
+			kick = 50;
+
+		VectorSubtract (client->damage_from, player->s.origin, v);
+		VectorNormalize (v);
+		
+		side = DotProduct (v, right);
+		client->v_dmg_roll = kick*side*0.3;
+		
+		side = -DotProduct (v, forward);
+		client->v_dmg_pitch = kick*side*0.3;
+
+		client->v_dmg_time = level.time + DAMAGE_TIME;
+	}
+
+	//
+	// clear totals
+	//
+	client->damage_blood = 0;
+	client->damage_armor = 0;
+	client->damage_parmor = 0;
+	client->damage_knockback = 0;
+}
+
+
+
+
+/*
+===============
+SV_CalcViewOffset
+
+Auto pitching on slopes?
+
+  fall from 128: 400 = 160000
+  fall from 256: 580 = 336400
+  fall from 384: 720 = 518400
+  fall from 512: 800 = 640000
+  fall from 640: 960 = 
+
+  damage = deltavelocity*deltavelocity  * 0.0001
+
+===============
+*/
+void SV_CalcViewOffset (edict_t *ent)
+{
+	float		*angles;
+	float		bob;
+	float		ratio;
+	float		delta;
+	vec3_t		v;
+
+
+//===================================
+
+	// base angles
+	angles = ent->client->ps.kick_angles;
+
+	// if dead, fix the angle and don't add any kick
+	if (ent->deadflag)
+	{
+		VectorClear (angles);
+
+		ent->client->ps.viewangles[ROLL] = 40;
+		ent->client->ps.viewangles[PITCH] = -15;
+		ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
+	}
+	else
+	{
+		// add angles based on weapon kick
+
+		VectorCopy (ent->client->kick_angles, angles);
+
+		// add angles based on damage kick
+
+		ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME;
+		if (ratio < 0)
+		{
+			ratio = 0;
+			ent->client->v_dmg_pitch = 0;
+			ent->client->v_dmg_roll = 0;
+		}
+		angles[PITCH] += ratio * ent->client->v_dmg_pitch;
+		angles[ROLL] += ratio * ent->client->v_dmg_roll;
+
+		// add pitch based on fall kick
+
+		ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+		if (ratio < 0)
+			ratio = 0;
+		angles[PITCH] += ratio * ent->client->fall_value;
+
+		// add angles based on velocity
+
+		delta = DotProduct (ent->velocity, forward);
+		angles[PITCH] += delta*run_pitch->value;
+		
+		delta = DotProduct (ent->velocity, right);
+		angles[ROLL] += delta*run_roll->value;
+
+		// add angles based on bob
+
+		delta = bobfracsin * bob_pitch->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		angles[PITCH] += delta;
+		delta = bobfracsin * bob_roll->value * xyspeed;
+		if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			delta *= 6;		// crouching
+		if (bobcycle & 1)
+			delta = -delta;
+		angles[ROLL] += delta;
+	}
+
+//===================================
+
+	// base origin
+
+	VectorClear (v);
+
+	// add view height
+
+	v[2] += ent->viewheight;
+
+	// add fall height
+
+	ratio = (ent->client->fall_time - level.time) / FALL_TIME;
+	if (ratio < 0)
+		ratio = 0;
+	v[2] -= ratio * ent->client->fall_value * 0.4;
+
+	// add bob height
+
+	bob = bobfracsin * xyspeed * bob_up->value;
+	if (bob > 6)
+		bob = 6;
+	//gi.DebugGraph (bob *2, 255);
+	v[2] += bob;
+
+	// add kick offset
+
+	VectorAdd (v, ent->client->kick_origin, v);
+
+	// absolutely bound offsets
+	// so the view can never be outside the player box
+
+	if (v[0] < -14)
+		v[0] = -14;
+	else if (v[0] > 14)
+		v[0] = 14;
+	if (v[1] < -14)
+		v[1] = -14;
+	else if (v[1] > 14)
+		v[1] = 14;
+	if (v[2] < -22)
+		v[2] = -22;
+	else if (v[2] > 30)
+		v[2] = 30;
+
+	VectorCopy (v, ent->client->ps.viewoffset);
+}
+
+/*
+==============
+SV_CalcGunOffset
+==============
+*/
+void SV_CalcGunOffset (edict_t *ent)
+{
+	int		i;
+	float	delta;
+
+	// gun angles from bobbing
+	ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005;
+	ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01;
+	if (bobcycle & 1)
+	{
+		ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
+		ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW];
+	}
+
+	ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005;
+
+	// gun angles from delta movement
+	for (i=0 ; i<3 ; i++)
+	{
+		delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i];
+		if (delta > 180)
+			delta -= 360;
+		if (delta < -180)
+			delta += 360;
+		if (delta > 45)
+			delta = 45;
+		if (delta < -45)
+			delta = -45;
+		if (i == YAW)
+			ent->client->ps.gunangles[ROLL] += 0.1*delta;
+		ent->client->ps.gunangles[i] += 0.2 * delta;
+	}
+
+	// gun height
+	VectorClear (ent->client->ps.gunoffset);
+//	ent->ps->gunorigin[2] += bob;
+
+	// gun_x / gun_y / gun_z are development tools
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->ps.gunoffset[i] += forward[i]*(gun_y->value);
+		ent->client->ps.gunoffset[i] += right[i]*gun_x->value;
+		ent->client->ps.gunoffset[i] += up[i]* (-gun_z->value);
+	}
+}
+
+
+/*
+=============
+SV_AddBlend
+=============
+*/
+void SV_AddBlend (float r, float g, float b, float a, float *v_blend)
+{
+	float	a2, a3;
+
+	if (a <= 0)
+		return;
+	a2 = v_blend[3] + (1-v_blend[3])*a;	// new total alpha
+	a3 = v_blend[3]/a2;		// fraction of color from old
+
+	v_blend[0] = v_blend[0]*a3 + r*(1-a3);
+	v_blend[1] = v_blend[1]*a3 + g*(1-a3);
+	v_blend[2] = v_blend[2]*a3 + b*(1-a3);
+	v_blend[3] = a2;
+}
+
+
+/*
+=============
+SV_CalcBlend
+=============
+*/
+void SV_CalcBlend (edict_t *ent)
+{
+	int		contents;
+	vec3_t	vieworg;
+	int		remaining;
+
+	ent->client->ps.blend[0] = ent->client->ps.blend[1] = 
+		ent->client->ps.blend[2] = ent->client->ps.blend[3] = 0;
+
+	// add for contents
+	VectorAdd (ent->s.origin, ent->client->ps.viewoffset, vieworg);
+	contents = gi.pointcontents (vieworg);
+	if (contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER) )
+		ent->client->ps.rdflags |= RDF_UNDERWATER;
+	else
+		ent->client->ps.rdflags &= ~RDF_UNDERWATER;
+
+	if (contents & (CONTENTS_SOLID|CONTENTS_LAVA))
+		SV_AddBlend (1.0, 0.3, 0.0, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_SLIME)
+		SV_AddBlend (0.0, 0.1, 0.05, 0.6, ent->client->ps.blend);
+	else if (contents & CONTENTS_WATER)
+		SV_AddBlend (0.5, 0.3, 0.2, 0.4, ent->client->ps.blend);
+
+	// add for powerups
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 0, 1, 0.08, ent->client->ps.blend);
+	}
+	// RAFAEL
+	else if (ent->client->quadfire_framenum > level.framenum)
+	{
+		remaining = ent->client->quadfire_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (1, 0.2, 0.5, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->invincible_framenum > level.framenum)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect2.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (1, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->enviro_framenum > level.framenum)
+	{
+		remaining = ent->client->enviro_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0, 1, 0, 0.08, ent->client->ps.blend);
+	}
+	else if (ent->client->breather_framenum > level.framenum)
+	{
+		remaining = ent->client->breather_framenum - level.framenum;
+		if (remaining == 30)	// beginning to fade
+			gi.sound(ent, CHAN_ITEM, gi.soundindex("items/airout.wav"), 1, ATTN_NORM, 0);
+		if (remaining > 30 || (remaining & 4) )
+			SV_AddBlend (0.4, 1, 0.4, 0.04, ent->client->ps.blend);
+	}
+
+	// add for damage
+	if (ent->client->damage_alpha > 0)
+		SV_AddBlend (ent->client->damage_blend[0],ent->client->damage_blend[1]
+		,ent->client->damage_blend[2], ent->client->damage_alpha, ent->client->ps.blend);
+
+	if (ent->client->bonus_alpha > 0)
+		SV_AddBlend (0.85, 0.7, 0.3, ent->client->bonus_alpha, ent->client->ps.blend);
+
+	// drop the damage value
+	ent->client->damage_alpha -= 0.06;
+	if (ent->client->damage_alpha < 0)
+		ent->client->damage_alpha = 0;
+
+	// drop the bonus value
+	ent->client->bonus_alpha -= 0.1;
+	if (ent->client->bonus_alpha < 0)
+		ent->client->bonus_alpha = 0;
+}
+
+
+/*
+=================
+P_FallingDamage
+=================
+*/
+void P_FallingDamage (edict_t *ent)
+{
+	float	delta;
+	int		damage;
+	vec3_t	dir;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+	if (ent->movetype == MOVETYPE_NOCLIP)
+		return;
+
+	if ((ent->client->oldvelocity[2] < 0) && (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity))
+	{
+		delta = ent->client->oldvelocity[2];
+	}
+	else
+	{
+		if (!ent->groundentity)
+			return;
+		delta = ent->velocity[2] - ent->client->oldvelocity[2];
+	}
+	delta = delta*delta * 0.0001;
+
+	// never take falling damage if completely underwater
+	if (ent->waterlevel == 3)
+		return;
+	if (ent->waterlevel == 2)
+		delta *= 0.25;
+	if (ent->waterlevel == 1)
+		delta *= 0.5;
+
+	if (delta < 1)
+		return;
+
+	if (delta < 15)
+	{
+		ent->s.event = EV_FOOTSTEP;
+		return;
+	}
+
+	ent->client->fall_value = delta*0.5;
+	if (ent->client->fall_value > 40)
+		ent->client->fall_value = 40;
+	ent->client->fall_time = level.time + FALL_TIME;
+
+	if (delta > 30)
+	{
+		if (ent->health > 0)
+		{
+			if (delta >= 55)
+				ent->s.event = EV_FALLFAR;
+			else
+				ent->s.event = EV_FALL;
+		}
+		ent->pain_debounce_time = level.time;	// no normal pain sound
+		damage = (delta-30)/2;
+		if (damage < 1)
+			damage = 1;
+		VectorSet (dir, 0, 0, 1);
+
+		if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING) )
+			T_Damage (ent, WORLD, WORLD, dir, ent->s.origin, vec3_origin, damage, 0, 0, MOD_FALLING);
+	}
+	else
+	{
+		ent->s.event = EV_FALLSHORT;
+		return;
+	}
+}
+
+
+
+/*
+=============
+P_WorldEffects
+=============
+*/
+void P_WorldEffects (void)
+{
+	qboolean	breather;
+	qboolean	envirosuit;
+	int			waterlevel, old_waterlevel;
+
+	if (current_player->movetype == MOVETYPE_NOCLIP)
+	{
+		current_player->air_finished = level.time + 12;	// don't need air
+		return;
+	}
+
+	waterlevel = current_player->waterlevel;
+	old_waterlevel = current_client->old_waterlevel;
+	current_client->old_waterlevel = waterlevel;
+
+	breather = current_client->breather_framenum > level.framenum;
+	envirosuit = current_client->enviro_framenum > level.framenum;
+
+	//
+	// if just entered a water volume, play a sound
+	//
+	if (!old_waterlevel && waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		if (current_player->watertype & CONTENTS_LAVA)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_SLIME)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		else if (current_player->watertype & CONTENTS_WATER)
+			gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0);
+		current_player->flags |= FL_INWATER;
+
+		// clear damage_debounce, so the pain sound will play immediately
+		current_player->damage_debounce_time = level.time - 1;
+	}
+
+	//
+	// if just completely exited a water volume, play a sound
+	//
+	if (old_waterlevel && ! waterlevel)
+	{
+		PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0);
+		current_player->flags &= ~FL_INWATER;
+	}
+
+	//
+	// check for head just going under water
+	//
+	if (old_waterlevel != 3 && waterlevel == 3)
+	{
+		gi.sound (current_player, CHAN_BODY, gi.soundindex("player/watr_un.wav"), 1, ATTN_NORM, 0);
+	}
+
+	//
+	// check for head just coming out of water
+	//
+	if (old_waterlevel == 3 && waterlevel != 3)
+	{
+		if (current_player->air_finished < level.time)
+		{	// gasp for air
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0);
+			PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+		}
+		else  if (current_player->air_finished < level.time + 11)
+		{	// just break surface
+			gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0);
+		}
+	}
+
+	//
+	// check for drowning
+	//
+	if (waterlevel == 3)
+	{
+		// breather or envirosuit give air
+		if (breather || envirosuit)
+		{
+			current_player->air_finished = level.time + 10;
+
+			if (((int)(current_client->breather_framenum - level.framenum) % 25) == 0)
+			{
+				if (!current_client->breather_sound)
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0);
+				current_client->breather_sound ^= 1;
+				PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF);
+				//FIXME: release a bubble?
+			}
+		}
+
+		// if out of air, start drowning
+		if (current_player->air_finished < level.time)
+		{	// drown!
+			if (current_player->client->next_drown_time < level.time 
+				&& current_player->health > 0)
+			{
+				current_player->client->next_drown_time = level.time + 1;
+
+				// take more damage the longer underwater
+				current_player->dmg += 2;
+				if (current_player->dmg > 15)
+					current_player->dmg = 15;
+
+				// play a gurp sound instead of a normal pain sound
+				if (current_player->health <= current_player->dmg)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
+				else if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0);
+
+				current_player->pain_debounce_time = level.time;
+
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, current_player->dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER);
+			}
+		}
+	}
+	else
+	{
+		current_player->air_finished = level.time + 12;
+		current_player->dmg = 2;
+	}
+
+	//
+	// check for sizzle damage
+	//
+	if (waterlevel && (current_player->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+	{
+		if (current_player->watertype & CONTENTS_LAVA)
+		{
+			if (current_player->health > 0
+				&& current_player->pain_debounce_time <= level.time
+				&& current_client->invincible_framenum < level.framenum)
+			{
+				if (rand()&1)
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0);
+				else
+					gi.sound (current_player, CHAN_VOICE, gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0);
+				current_player->pain_debounce_time = level.time + 1;
+			}
+
+			if (envirosuit)	// take 1/3 damage with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_LAVA);
+			else
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 3*waterlevel, 0, 0, MOD_LAVA);
+		}
+
+		if (current_player->watertype & CONTENTS_SLIME)
+		{
+			if (!envirosuit)
+			{	// no damage from slime with envirosuit
+				T_Damage (current_player, WORLD, WORLD, vec3_origin, current_player->s.origin, vec3_origin, 1*waterlevel, 0, 0, MOD_SLIME);
+			}
+		}
+	}
+}
+
+
+/*
+===============
+G_SetClientEffects
+===============
+*/
+void G_SetClientEffects (edict_t *ent)
+{
+	int		pa_type;
+	int		remaining;
+
+	ent->s.effects = 0;
+	ent->s.renderfx = 0;
+
+	if (ent->health <= 0 || level.intermissiontime)
+		return;
+
+	if (ent->powerarmor_time > level.time)
+	{
+		pa_type = PowerArmorType (ent);
+		if (pa_type == POWER_ARMOR_SCREEN)
+		{
+			ent->s.effects |= EF_POWERSCREEN;
+		}
+		else if (pa_type == POWER_ARMOR_SHIELD)
+		{
+			ent->s.effects |= EF_COLOR_SHELL;
+			ent->s.renderfx |= RF_SHELL_GREEN;
+		}
+	}
+
+	if (ent->client->quad_framenum > level.framenum)
+	{
+		remaining = ent->client->quad_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_QUAD;
+	}
+
+	// RAFAEL
+	if (ent->client->quadfire_framenum > level.framenum)
+	{
+		remaining = ent->client->quadfire_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_QUAD;
+	}
+
+	
+	if (ent->client->invincible_framenum > level.framenum)
+	{
+		remaining = ent->client->invincible_framenum - level.framenum;
+		if (remaining > 30 || (remaining & 4) )
+			ent->s.effects |= EF_PENT;
+	}
+
+	// show cheaters!!!
+	if (ent->flags & FL_GODMODE)
+	{
+		ent->s.effects |= EF_COLOR_SHELL;
+		ent->s.renderfx |= (RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE);
+	}
+}
+
+
+/*
+===============
+G_SetClientEvent
+===============
+*/
+void G_SetClientEvent (edict_t *ent)
+{
+	if (ent->s.event)
+		return;
+
+	if ( ent->groundentity && xyspeed > 225)
+	{
+		if ( (int)(current_client->bobtime+bobmove) != bobcycle )
+			ent->s.event = EV_FOOTSTEP;
+	}
+}
+
+/*
+===============
+G_SetClientSound
+===============
+*/
+void G_SetClientSound (edict_t *ent)
+{
+	char	*weap;
+
+	if (ent->client->pers.game_helpchanged != game.helpchanged)
+	{
+		ent->client->pers.game_helpchanged = game.helpchanged;
+		ent->client->pers.helpchanged = 1;
+	}
+
+	// help beep (no more than three times)
+	if (ent->client->pers.helpchanged && ent->client->pers.helpchanged <= 3 && !(level.framenum&63) )
+	{
+		ent->client->pers.helpchanged++;
+		gi.sound (ent, CHAN_VOICE, gi.soundindex ("misc/pc_up.wav"), 1, ATTN_STATIC, 0);
+	}
+
+
+	if (ent->client->pers.weapon)
+		weap = ent->client->pers.weapon->classname;
+	else
+		weap = "";
+
+	if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
+		ent->s.sound = snd_fry;
+	else if (strcmp(weap, "weapon_railgun") == 0)
+		ent->s.sound = gi.soundindex("weapons/rg_hum.wav");
+	else if (strcmp(weap, "weapon_bfg") == 0)
+		ent->s.sound = gi.soundindex("weapons/bfg_hum.wav");
+	// RAFAEL
+	else if (strcmp (weap, "weapon_phalanx") == 0)
+		ent->s.sound = gi.soundindex ("weapons/phaloop.wav");
+	else if (ent->client->weapon_sound)
+		ent->s.sound = ent->client->weapon_sound;
+	else
+		ent->s.sound = 0;
+}
+
+/*
+===============
+G_SetClientFrame
+===============
+*/
+void G_SetClientFrame (edict_t *ent)
+{
+	gclient_t	*client;
+	qboolean	duck, run;
+
+	if (ent->s.modelindex != 255)
+		return;		// not in the player model
+
+	client = ent->client;
+
+	if (client->ps.pmove.pm_flags & PMF_DUCKED)
+		duck = true;
+	else
+		duck = false;
+	if (xyspeed)
+		run = true;
+	else
+		run = false;
+
+	// check for stand/duck and stop/go transitions
+	if (duck != client->anim_duck && client->anim_priority < ANIM_DEATH)
+		goto newanim;
+	if (run != client->anim_run && client->anim_priority == ANIM_BASIC)
+		goto newanim;
+	if (!ent->groundentity && client->anim_priority <= ANIM_WAVE)
+		goto newanim;
+
+	if(client->anim_priority == ANIM_REVERSE)
+	{
+		if(ent->s.frame > client->anim_end)
+		{
+			ent->s.frame--;
+			return;
+		}
+	}
+	else if (ent->s.frame < client->anim_end)
+	{	// continue an animation
+		ent->s.frame++;
+		return;
+	}
+
+	if (client->anim_priority == ANIM_DEATH)
+		return;		// stay there
+	if (client->anim_priority == ANIM_JUMP)
+	{
+		if (!ent->groundentity)
+			return;		// stay there
+		ent->client->anim_priority = ANIM_WAVE;
+		ent->s.frame = FRAME_jump3;
+		ent->client->anim_end = FRAME_jump6;
+		return;
+	}
+
+newanim:
+	// return to either a running or standing frame
+	client->anim_priority = ANIM_BASIC;
+	client->anim_duck = duck;
+	client->anim_run = run;
+
+	if (!ent->groundentity)
+	{
+		client->anim_priority = ANIM_JUMP;
+		if (ent->s.frame != FRAME_jump2)
+			ent->s.frame = FRAME_jump1;
+		client->anim_end = FRAME_jump2;
+	}
+	else if (run)
+	{	// running
+		if (duck)
+		{
+			ent->s.frame = FRAME_crwalk1;
+			client->anim_end = FRAME_crwalk6;
+		}
+		else
+		{
+			ent->s.frame = FRAME_run1;
+			client->anim_end = FRAME_run6;
+		}
+	}
+	else
+	{	// standing
+		if (duck)
+		{
+			ent->s.frame = FRAME_crstnd01;
+			client->anim_end = FRAME_crstnd19;
+		}
+		else
+		{
+			ent->s.frame = FRAME_stand01;
+			client->anim_end = FRAME_stand40;
+		}
+	}
+}
+
+
+/*
+=================
+ClientEndServerFrame
+
+Called for each player at the end of the server frame
+and right after spawning
+=================
+*/
+void ClientEndServerFrame (edict_t *ent)
+{
+	float	bobtime;
+	int		i;
+
+	current_player = ent;
+	current_client = ent->client;
+
+	//
+	// If the origin or velocity have changed since ClientThink(),
+	// update the pmove values.  This will happen when the client
+	// is pushed by a bmodel or kicked by an explosion.
+	// 
+	// If it wasn't updated here, the view position would lag a frame
+	// behind the body position when pushed -- "sinking into plats"
+	//
+	for (i=0 ; i<3 ; i++)
+	{
+		current_client->ps.pmove.origin[i] = ent->s.origin[i]*8.0;
+		current_client->ps.pmove.velocity[i] = ent->velocity[i]*8.0;
+	}
+
+	//
+	// If the end of unit layout is displayed, don't give
+	// the player any normal movement attributes
+	//
+	if (level.intermissiontime)
+	{
+		// FIXME: add view drifting here?
+		current_client->ps.blend[3] = 0;
+		current_client->ps.fov = 90;
+		G_SetStats (ent);
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, up);
+
+	// burn from lava, etc
+	P_WorldEffects ();
+
+	//
+	// set model angles from view angles so other things in
+	// the world can tell which direction you are looking
+	//
+	if (ent->client->v_angle[PITCH] > 180)
+		ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH])/3;
+	else
+		ent->s.angles[PITCH] = ent->client->v_angle[PITCH]/3;
+	ent->s.angles[YAW] = ent->client->v_angle[YAW];
+	ent->s.angles[ROLL] = 0;
+	ent->s.angles[ROLL] = SV_CalcRoll (ent->s.angles, ent->velocity)*4;
+
+	//
+	// calculate speed and cycle to be used for
+	// all cyclic walking effects
+	//
+	xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);
+
+	if (xyspeed < 5)
+	{
+		bobmove = 0;
+		current_client->bobtime = 0;	// start at beginning of cycle again
+	}
+	else if (ent->groundentity)
+	{	// so bobbing only cycles when on ground
+		if (xyspeed > 210)
+			bobmove = 0.25;
+		else if (xyspeed > 100)
+			bobmove = 0.125;
+		else
+			bobmove = 0.0625;
+	}
+	
+	bobtime = (current_client->bobtime += bobmove);
+
+	if (current_client->ps.pmove.pm_flags & PMF_DUCKED)
+		bobtime *= 4;
+
+	bobcycle = (int)bobtime;
+	bobfracsin = fabs(sin(bobtime*M_PI));
+
+	// detect hitting the floor
+	P_FallingDamage (ent);
+
+	// apply all the damage taken this frame
+	P_DamageFeedback (ent);
+
+	// determine the view offsets
+	SV_CalcViewOffset (ent);
+
+	// determine the gun offsets
+	SV_CalcGunOffset (ent);
+
+	// determine the full screen color blend
+	// must be after viewoffset, so eye contents can be
+	// accurately determined
+	// FIXME: with client prediction, the contents
+	// should be determined by the client
+	SV_CalcBlend (ent);
+
+	// chase cam stuff
+	if (ent->client->resp.spectator)
+		G_SetSpectatorStats(ent);
+	else
+		G_SetStats (ent);
+	G_CheckChaseStats(ent);
+
+	G_SetClientEvent (ent);
+
+	G_SetClientEffects (ent);
+
+	G_SetClientSound (ent);
+
+	G_SetClientFrame (ent);
+
+	VectorCopy (ent->velocity, ent->client->oldvelocity);
+	VectorCopy (ent->client->ps.viewangles, ent->client->oldviewangles);
+
+	// clear weapon kicks
+	VectorClear (ent->client->kick_origin);
+	VectorClear (ent->client->kick_angles);
+
+	// if the scoreboard is up, update it
+	if (ent->client->showscores && !(level.framenum & 31) )
+	{
+		DeathmatchScoreboardMessage (ent, ent->enemy);
+		gi.unicast (ent, false);
+	}
+}
+
--- /dev/null
+++ b/xatrix/p_weapon.c
@@ -1,0 +1,1867 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+#include "m_player.h"
+
+
+static qboolean	is_quad;
+// RAFAEL
+static qboolean is_quadfire;
+static byte		is_silenced;
+
+
+void weapon_grenade_fire (edict_t *ent, qboolean held);
+// RAFAEL
+void weapon_trap_fire (edict_t *ent, qboolean held);
+
+
+static void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result)
+{
+	vec3_t	_distance;
+
+	VectorCopy (distance, _distance);
+	if (client->pers.hand == LEFT_HANDED)
+		_distance[1] *= -1;
+	else if (client->pers.hand == CENTER_HANDED)
+		_distance[1] = 0;
+	G_ProjectSource (point, _distance, forward, right, result);
+}
+
+
+/*
+===============
+PlayerNoise
+
+Each player can have two noise objects associated with it:
+a personal noise (jumping, pain, weapon firing), and a weapon
+target noise (bullet wall impacts)
+
+Monsters that don't directly see the player can move
+to a noise in hopes of seeing the player from there.
+===============
+*/
+void PlayerNoise(edict_t *who, vec3_t where, int type)
+{
+	edict_t		*noise;
+
+	if (type == PNOISE_WEAPON)
+	{
+		if (who->client->silencer_shots)
+		{
+			who->client->silencer_shots--;
+			return;
+		}
+	}
+
+	if (deathmatch->value)
+		return;
+
+	if (who->flags & FL_NOTARGET)
+		return;
+
+
+	if (!who->mynoise)
+	{
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise = noise;
+
+		noise = G_Spawn();
+		noise->classname = "player_noise";
+		VectorSet (noise->mins, -8, -8, -8);
+		VectorSet (noise->maxs, 8, 8, 8);
+		noise->owner = who;
+		noise->svflags = SVF_NOCLIENT;
+		who->mynoise2 = noise;
+	}
+
+	if (type == PNOISE_SELF || type == PNOISE_WEAPON)
+	{
+		noise = who->mynoise;
+		level.sound_entity = noise;
+		level.sound_entity_framenum = level.framenum;
+	}
+	else // type == PNOISE_IMPACT
+	{
+		noise = who->mynoise2;
+		level.sound2_entity = noise;
+		level.sound2_entity_framenum = level.framenum;
+	}
+
+	VectorCopy (where, noise->s.origin);
+	VectorSubtract (where, noise->maxs, noise->absmin);
+	VectorAdd (where, noise->maxs, noise->absmax);
+	noise->teleport_time = level.time;
+	gi.linkentity (noise);
+}
+
+
+qboolean Pickup_Weapon (edict_t *ent, edict_t *other)
+{
+	int			index;
+	gitem_t		*ammo;
+
+	index = ITEM_INDEX(ent->item);
+
+	if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) 
+		&& other->client->pers.inventory[index])
+	{
+		if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) )
+			return false;	// leave the weapon for others to pickup
+	}
+
+	other->client->pers.inventory[index]++;
+
+	if (!(ent->spawnflags & DROPPED_ITEM) )
+	{
+		// give them some ammo with it
+		ammo = FindItem (ent->item->ammo);
+		// Don't get infinite ammo with trap
+		if ( ((int)dmflags->value & DF_INFINITE_AMMO) && cistrcmp(ent->item->pickup_name, "ammo_trap") )
+			Add_Ammo (other, ammo, 1000);
+		else
+			Add_Ammo (other, ammo, ammo->quantity);
+
+		if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) )
+		{
+			if (deathmatch->value)
+			{
+				if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+					ent->flags |= FL_RESPAWN;
+				else
+					SetRespawn (ent, 30);
+			}
+			if (coop->value)
+				ent->flags |= FL_RESPAWN;
+		}
+	}
+
+	if (other->client->pers.weapon != ent->item && 
+		(other->client->pers.inventory[index] == 1) &&
+		( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) )
+		other->client->newweapon = ent->item;
+
+	return true;
+}
+
+
+/*
+===============
+ChangeWeapon
+
+The old weapon has been dropped all the way, so make the new one
+current
+===============
+*/
+void ChangeWeapon (edict_t *ent)
+{
+	int i;
+
+	if (ent->client->grenade_time)
+	{
+		ent->client->grenade_time = level.time;
+		ent->client->weapon_sound = 0;
+		weapon_grenade_fire (ent, false);
+		ent->client->grenade_time = 0;
+	}
+
+	ent->client->pers.lastweapon = ent->client->pers.weapon;
+	ent->client->pers.weapon = ent->client->newweapon;
+	ent->client->newweapon = NULL;
+	ent->client->machinegun_shots = 0;
+
+	// set visible model
+	if (ent->s.modelindex == 255)
+	{
+		if (ent->client->pers.weapon)
+			i = ((ent->client->pers.weapon->weapmodel & 0xff) << 8);
+		else
+			i = 0;
+		ent->s.skinnum = (ent - g_edicts - 1) | i;
+	}
+
+	if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
+		ent->client->ammo_index = ITEM_INDEX(FindItem(ent->client->pers.weapon->ammo));
+	else
+		ent->client->ammo_index = 0;
+
+	if (!ent->client->pers.weapon)
+	{	// dead
+		ent->client->ps.gunindex = 0;
+		return;
+	}
+
+	ent->client->weaponstate = WEAPON_ACTIVATING;
+	ent->client->ps.gunframe = 0;
+	ent->client->ps.gunindex = gi.modelindex(ent->client->pers.weapon->view_model);
+
+	ent->client->anim_priority = ANIM_PAIN;
+	if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+			ent->s.frame = FRAME_crpain1;
+			ent->client->anim_end = FRAME_crpain4;
+	}
+	else
+	{
+			ent->s.frame = FRAME_pain301;
+			ent->client->anim_end = FRAME_pain304;
+			
+	}
+}
+
+/*
+=================
+NoAmmoWeaponChange
+=================
+*/
+void NoAmmoWeaponChange (edict_t *ent)
+{
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("slugs"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("railgun"))] )
+	{
+		ent->client->newweapon = FindItem ("railgun");
+		return;
+	}
+	// RAFAEL
+	if ( ent->client->pers.inventory[ITEM_INDEX (FindItem ("mag slug"))]
+		&& ent->client->pers.inventory[ITEM_INDEX (FindItem ("phalanx"))])
+	{
+		ent->client->newweapon = FindItem ("phalanx");	
+	}
+	// RAFAEL
+	if ( ent->client->pers.inventory[ITEM_INDEX (FindItem ("cells"))]
+		&& ent->client->pers.inventory[ITEM_INDEX (FindItem ("ionripper"))])
+	{
+		ent->client->newweapon = FindItem ("ionrippergun");	
+	}
+	
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("hyperblaster"))] )
+	{
+		ent->client->newweapon = FindItem ("hyperblaster");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("chaingun"))] )
+	{
+		ent->client->newweapon = FindItem ("chaingun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("bullets"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("machinegun"))] )
+	{
+		ent->client->newweapon = FindItem ("machinegun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))] > 1
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("super shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("super shotgun");
+		return;
+	}
+	if ( ent->client->pers.inventory[ITEM_INDEX(FindItem("shells"))]
+		&&  ent->client->pers.inventory[ITEM_INDEX(FindItem("shotgun"))] )
+	{
+		ent->client->newweapon = FindItem ("shotgun");
+		return;
+	}
+	ent->client->newweapon = FindItem ("blaster");
+}
+
+/*
+=================
+Think_Weapon
+
+Called by ClientBeginServerFrame and ClientThink
+=================
+*/
+void Think_Weapon (edict_t *ent)
+{
+	// if just died, put the weapon away
+	if (ent->health < 1)
+	{
+		ent->client->newweapon = NULL;
+		ChangeWeapon (ent);
+	}
+
+	// call active weapon think routine
+	if (ent->client->pers.weapon && ent->client->pers.weapon->weaponthink)
+	{
+		is_quad = (ent->client->quad_framenum > level.framenum);
+		// RAFAEL
+		is_quadfire = (ent->client->quadfire_framenum > level.framenum);
+		if (ent->client->silencer_shots)
+			is_silenced = MZ_SILENCED;
+		else
+			is_silenced = 0;
+		ent->client->pers.weapon->weaponthink (ent);
+	}
+}
+
+
+/*
+================
+Use_Weapon
+
+Make the weapon ready if there is ammo
+================
+*/
+void Use_Weapon (edict_t *ent, gitem_t *item)
+{
+	int			ammo_index;
+	gitem_t		*ammo_item;
+
+	// see if we're already using it
+	if (item == ent->client->pers.weapon)
+		return;
+
+	if (item->ammo && !g_select_empty->value && !(item->flags & IT_AMMO))
+	{
+		ammo_item = FindItem(item->ammo);
+		ammo_index = ITEM_INDEX(ammo_item);
+
+		if (!ent->client->pers.inventory[ammo_index])
+		{
+			gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+			return;
+		}
+
+		if (ent->client->pers.inventory[ammo_index] < item->quantity)
+		{
+			gi.cprintf (ent, PRINT_HIGH, "Not enough %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+			return;
+		}
+	}
+
+	// change to this weapon when down
+	ent->client->newweapon = item;
+}
+
+// RAFAEL 14-APR-98
+void Use_Weapon2 (edict_t *ent, gitem_t *item)
+{
+	int			ammo_index;
+	gitem_t		*ammo_item;
+	gitem_t		*nextitem;
+	int			index;
+
+	if (strcmp (item->pickup_name, "HyperBlaster") == 0)
+	{
+		if (item == ent->client->pers.weapon)
+		{
+			item = FindItem ("Ionripper");
+			index = ITEM_INDEX (item);
+			if (!ent->client->pers.inventory[index])
+			{
+				item = FindItem ("HyperBlaster");
+			}
+		}
+	}
+	
+	else if (strcmp (item->pickup_name, "Railgun") == 0)
+  	{
+		ammo_item = FindItem(item->ammo);
+		ammo_index = ITEM_INDEX(ammo_item);
+		if (!ent->client->pers.inventory[ammo_index])
+		{
+			nextitem = FindItem ("Phalanx");
+			ammo_item = FindItem(nextitem->ammo);
+			ammo_index = ITEM_INDEX(ammo_item);
+			if (ent->client->pers.inventory[ammo_index])
+			{
+				item = FindItem ("Phalanx");
+				index = ITEM_INDEX (item);
+				if (!ent->client->pers.inventory[index])
+				{
+					item = FindItem ("Railgun");
+				}
+			}
+		}
+		else if (item == ent->client->pers.weapon)
+		{
+			item = FindItem ("Phalanx");
+			index = ITEM_INDEX (item);
+			if (!ent->client->pers.inventory[index])
+			{
+				item = FindItem ("Railgun");
+			}
+		}
+				
+	}
+
+	
+	// see if we're already using it
+	if (item == ent->client->pers.weapon)
+		return;
+	
+	if (item->ammo)
+	{
+		ammo_item = FindItem(item->ammo);
+		ammo_index = ITEM_INDEX(ammo_item);
+		if (!ent->client->pers.inventory[ammo_index] && !g_select_empty->value)
+		{
+			gi.cprintf (ent, PRINT_HIGH, "No %s for %s.\n", ammo_item->pickup_name, item->pickup_name);
+			return;
+		}
+	}
+
+	// change to this weapon when down
+	ent->client->newweapon = item;
+	
+}
+// END 14-APR-98
+
+/*
+================
+Drop_Weapon
+================
+*/
+void Drop_Weapon (edict_t *ent, gitem_t *item)
+{
+	int		index;
+
+	if ((int)(dmflags->value) & DF_WEAPONS_STAY)
+		return;
+
+	index = ITEM_INDEX(item);
+	// see if we're already using it
+	if ( ((item == ent->client->pers.weapon) || (item == ent->client->newweapon))&& (ent->client->pers.inventory[index] == 1) )
+	{
+		gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n");
+		return;
+	}
+
+	Drop_Item (ent, item);
+	ent->client->pers.inventory[index]--;
+}
+
+
+/*
+================
+Weapon_Generic
+
+A generic function to handle the basics of weapon thinking
+================
+*/
+#define FRAME_FIRE_FIRST		(FRAME_ACTIVATE_LAST + 1)
+#define FRAME_IDLE_FIRST		(FRAME_FIRE_LAST + 1)
+#define FRAME_DEACTIVATE_FIRST	(FRAME_IDLE_LAST + 1)
+
+void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent))
+{
+	int		n;
+
+	if (ent->client->weaponstate == WEAPON_DROPPING)
+	{
+		if (ent->client->ps.gunframe == FRAME_DEACTIVATE_LAST)
+		{
+			ChangeWeapon (ent);
+			return;
+		}
+		else if ((FRAME_DEACTIVATE_LAST - ent->client->ps.gunframe) == 4)
+		{
+			ent->client->anim_priority = ANIM_REVERSE;
+			if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crpain4+1;
+				ent->client->anim_end = FRAME_crpain1;
+			}
+			else
+			{
+				ent->s.frame = FRAME_pain304+1;
+				ent->client->anim_end = FRAME_pain301;
+				
+			}
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		if (ent->client->ps.gunframe == FRAME_ACTIVATE_LAST)
+		{
+			ent->client->weaponstate = WEAPON_READY;
+			ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+			return;
+		}
+
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if ((ent->client->newweapon) && (ent->client->weaponstate != WEAPON_FIRING))
+	{
+		ent->client->weaponstate = WEAPON_DROPPING;
+		ent->client->ps.gunframe = FRAME_DEACTIVATE_FIRST;
+
+		if ((FRAME_DEACTIVATE_LAST - FRAME_DEACTIVATE_FIRST) < 4)
+		{
+			ent->client->anim_priority = ANIM_REVERSE;
+			if(ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crpain4+1;
+				ent->client->anim_end = FRAME_crpain1;
+			}
+			else
+			{
+				ent->s.frame = FRAME_pain304+1;
+				ent->client->anim_end = FRAME_pain301;
+				
+			}
+		}
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if ((!ent->client->ammo_index) || 
+				( ent->client->pers.inventory[ent->client->ammo_index] >= ent->client->pers.weapon->quantity))
+			{
+				ent->client->ps.gunframe = FRAME_FIRE_FIRST;
+				ent->client->weaponstate = WEAPON_FIRING;
+
+				// start the animation
+				ent->client->anim_priority = ANIM_ATTACK;
+				if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+				{
+					ent->s.frame = FRAME_crattak1-1;
+					ent->client->anim_end = FRAME_crattak9;
+				}
+				else
+				{
+					ent->s.frame = FRAME_attack1-1;
+					ent->client->anim_end = FRAME_attack8;
+				}
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+		}
+		else
+		{
+			if (ent->client->ps.gunframe == FRAME_IDLE_LAST)
+			{
+				ent->client->ps.gunframe = FRAME_IDLE_FIRST;
+				return;
+			}
+
+			if (pause_frames)
+			{
+				for (n = 0; pause_frames[n]; n++)
+				{
+					if (ent->client->ps.gunframe == pause_frames[n])
+					{
+						if (rand()&15)
+							return;
+					}
+				}
+			}
+
+			ent->client->ps.gunframe++;
+			return;
+		}
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		for (n = 0; fire_frames[n]; n++)
+		{
+			if (ent->client->ps.gunframe == fire_frames[n])
+			{
+				if (ent->client->quad_framenum > level.framenum)
+					gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage3.wav"), 1, ATTN_NORM, 0);
+
+				fire (ent);
+				break;
+			}
+		}
+
+		if (!fire_frames[n])
+			ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == FRAME_IDLE_FIRST+1)
+			ent->client->weaponstate = WEAPON_READY;
+	}
+}
+
+
+/*
+======================================================================
+
+GRENADE
+
+======================================================================
+*/
+
+#define GRENADE_TIMER		3.0
+#define GRENADE_MINSPEED	400
+#define GRENADE_MAXSPEED	800
+
+void weapon_grenade_fire (edict_t *ent, qboolean held)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+	int		damage = 125;
+	float	timer;
+	int		speed;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+		damage *= 4;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	timer = ent->client->grenade_time - level.time;
+	speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+	fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->grenade_time = level.time + 1.0;
+
+	if (ent->health <= 0)
+		return;
+
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->client->anim_priority = ANIM_ATTACK;
+		ent->s.frame = FRAME_crattak1-1;
+		ent->client->anim_end = FRAME_crattak3;
+	}
+	else
+	{
+		ent->client->anim_priority = ANIM_REVERSE;
+		ent->s.frame = FRAME_wave08;
+		ent->client->anim_end = FRAME_wave01;
+	}
+}
+
+void Weapon_Grenade (edict_t *ent)
+{
+	if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+	{
+		ChangeWeapon (ent);
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		ent->client->weaponstate = WEAPON_READY;
+		ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if (ent->client->pers.inventory[ent->client->ammo_index])
+			{
+				ent->client->ps.gunframe = 1;
+				ent->client->weaponstate = WEAPON_FIRING;
+				ent->client->grenade_time = 0;
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+			return;
+		}
+
+		if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48))
+		{
+			if (rand()&15)
+				return;
+		}
+
+		if (++ent->client->ps.gunframe > 48)
+			ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->ps.gunframe == 5)
+			gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+
+		if (ent->client->ps.gunframe == 11)
+		{
+			if (!ent->client->grenade_time)
+			{
+				ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+				ent->client->weapon_sound = gi.soundindex("weapons/hgrenc1b.wav");
+			}
+
+			// they waited too long, detonate it in their hand
+			if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
+			{
+				ent->client->weapon_sound = 0;
+				weapon_grenade_fire (ent, true);
+				ent->client->grenade_blew_up = true;
+			}
+
+			if (ent->client->buttons & BUTTON_ATTACK)
+				return;
+
+			if (ent->client->grenade_blew_up)
+			{
+				if (level.time >= ent->client->grenade_time)
+				{
+					ent->client->ps.gunframe = 15;
+					ent->client->grenade_blew_up = false;
+				}
+				else
+				{
+					return;
+				}
+			}
+		}
+
+		if (ent->client->ps.gunframe == 12)
+		{
+			ent->client->weapon_sound = 0;
+			weapon_grenade_fire (ent, false);
+		}
+
+		if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time))
+			return;
+
+		ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == 16)
+		{
+			ent->client->grenade_time = 0;
+			ent->client->weaponstate = WEAPON_READY;
+		}
+	}
+}
+
+/*
+======================================================================
+
+GRENADE LAUNCHER
+
+======================================================================
+*/
+
+void weapon_grenadelauncher_fire (edict_t *ent)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+	int		damage = 120;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+		damage *= 4;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_GRENADE | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_GrenadeLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {34, 51, 59, 0};
+	static int	fire_frames[]	= {6, 0};
+
+	Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
+
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_grenadelauncher_fire);
+
+}
+
+/*
+======================================================================
+
+ROCKET
+
+======================================================================
+*/
+
+void Weapon_RocketLauncher_Fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius;
+	int		radius_damage;
+
+	damage = 100 + (int)(qrandom() * 20.0);
+	radius_damage = 120;
+	damage_radius = 120;
+	if (is_quad)
+	{
+		damage *= 4;
+		radius_damage *= 4;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_ROCKET | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_RocketLauncher (edict_t *ent)
+{
+	static int	pause_frames[]	= {25, 33, 42, 50, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 4, 12, 50, 54, pause_frames, fire_frames, Weapon_RocketLauncher_Fire);
+		
+
+}
+
+
+/*
+======================================================================
+
+BLASTER / HYPERBLASTER
+
+======================================================================
+*/
+
+void Blaster_Fire (edict_t *ent, vec3_t g_offset, int damage, qboolean hyper, int effect)
+{
+	vec3_t	forward, right;
+	vec3_t	start;
+	vec3_t	offset;
+
+	if (is_quad)
+		damage *= 4;
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	VectorSet(offset, 24, 8, ent->viewheight-8);
+	VectorAdd (offset, g_offset, offset);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -1;
+
+	fire_blaster (ent, start, forward, damage, 1000, effect, hyper);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	if (hyper)
+		gi.WriteByte (MZ_HYPERBLASTER | is_silenced);
+	else
+		gi.WriteByte (MZ_BLASTER | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+}
+
+
+void Weapon_Blaster_Fire (edict_t *ent)
+{
+	int		damage;
+
+	if (deathmatch->value)
+		damage = 15;
+	else
+		damage = 10;
+	Blaster_Fire (ent, vec3_origin, damage, false, EF_BLASTER);
+	ent->client->ps.gunframe++;
+}
+
+void Weapon_Blaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {19, 32, 0};
+	static int	fire_frames[]	= {5, 0};
+
+	Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
+
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Blaster_Fire);
+		
+}
+
+
+void Weapon_HyperBlaster_Fire (edict_t *ent)
+{
+	float	rotation;
+	vec3_t	offset;
+	int		effect;
+	int		damage;
+
+	ent->client->weapon_sound = gi.soundindex("weapons/hyprbl1a.wav");
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe++;
+	}
+	else
+	{
+		if (! ent->client->pers.inventory[ent->client->ammo_index] )
+		{
+			if (level.time >= ent->pain_debounce_time)
+			{
+				gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+				ent->pain_debounce_time = level.time + 1;
+			}
+			NoAmmoWeaponChange (ent);
+		}
+		else
+		{
+			rotation = (ent->client->ps.gunframe - 5) * 2*M_PI/6;
+			offset[0] = -4 * sin(rotation);
+			offset[1] = 0;
+			offset[2] = 4 * cos(rotation);
+
+			if ((ent->client->ps.gunframe == 6) || (ent->client->ps.gunframe == 9))
+				effect = EF_HYPERBLASTER;
+			else
+				effect = 0;
+			if (deathmatch->value)
+				damage = 15;
+			else
+				damage = 20;
+			Blaster_Fire (ent, offset, damage, true, effect);
+			if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+				ent->client->pers.inventory[ent->client->ammo_index]--;
+
+			ent->client->anim_priority = ANIM_ATTACK;
+			if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+			{
+				ent->s.frame = FRAME_crattak1 - 1;
+				ent->client->anim_end = FRAME_crattak9;
+			}
+			else
+			{
+				ent->s.frame = FRAME_attack1 - 1;
+				ent->client->anim_end = FRAME_attack8;
+			}
+		}
+
+		ent->client->ps.gunframe++;
+		if (ent->client->ps.gunframe == 12 && ent->client->pers.inventory[ent->client->ammo_index])
+			ent->client->ps.gunframe = 6;
+	}
+
+	if (ent->client->ps.gunframe == 12)
+	{
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0);
+		ent->client->weapon_sound = 0;
+	}
+
+}
+
+void Weapon_HyperBlaster (edict_t *ent)
+{
+	static int	pause_frames[]	= {0};
+	static int	fire_frames[]	= {6, 7, 8, 9, 10, 11, 0};
+
+	Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 5, 20, 49, 53, pause_frames, fire_frames, Weapon_HyperBlaster_Fire);
+		
+}
+
+/*
+======================================================================
+
+MACHINEGUN / CHAINGUN
+
+======================================================================
+*/
+
+void Machinegun_Fire (edict_t *ent)
+{
+	int	i;
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		angles;
+	int			damage = 8;
+	int			kick = 2;
+	vec3_t		offset;
+
+	if (!(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->machinegun_shots = 0;
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (ent->client->ps.gunframe == 5)
+		ent->client->ps.gunframe = 4;
+	else
+		ent->client->ps.gunframe = 5;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 1)
+	{
+		ent->client->ps.gunframe = 6;
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	for (i=1 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+	ent->client->kick_origin[0] = crandom() * 0.35;
+	ent->client->kick_angles[0] = ent->client->machinegun_shots * -1.5;
+
+	// raise the gun as it is firing
+	if (!deathmatch->value)
+	{
+		ent->client->machinegun_shots++;
+		if (ent->client->machinegun_shots > 9)
+			ent->client->machinegun_shots = 9;
+	}
+
+	// get start / end positions
+	VectorAdd (ent->client->v_angle, ent->client->kick_angles, angles);
+	AngleVectors (angles, forward, right, NULL);
+	VectorSet(offset, 0, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
+
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_MACHINEGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - (int) (qrandom()+0.25);
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - (int) (qrandom()+0.25);
+		ent->client->anim_end = FRAME_attack8;
+	}
+}
+
+void Weapon_Machinegun (edict_t *ent)
+{
+	static int	pause_frames[]	= {23, 45, 0};
+	static int	fire_frames[]	= {4, 5, 0};
+
+	Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire);
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 3, 5, 45, 49, pause_frames, fire_frames, Machinegun_Fire);
+	
+}
+
+void Chaingun_Fire (edict_t *ent)
+{
+	int			i;
+	int			shots;
+	vec3_t		start;
+	vec3_t		forward, right, up;
+	float		r, u;
+	vec3_t		offset;
+	int			damage;
+	int			kick = 2;
+
+	if (deathmatch->value)
+		damage = 6;
+	else
+		damage = 8;
+
+	if (ent->client->ps.gunframe == 5)
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnu1a.wav"), 1, ATTN_IDLE, 0);
+
+	if ((ent->client->ps.gunframe == 14) && !(ent->client->buttons & BUTTON_ATTACK))
+	{
+		ent->client->ps.gunframe = 32;
+		ent->client->weapon_sound = 0;
+		return;
+	}
+	else if ((ent->client->ps.gunframe == 21) && (ent->client->buttons & BUTTON_ATTACK)
+		&& ent->client->pers.inventory[ent->client->ammo_index])
+	{
+		ent->client->ps.gunframe = 15;
+	}
+	else
+	{
+		ent->client->ps.gunframe++;
+	}
+
+	if (ent->client->ps.gunframe == 22)
+	{
+		ent->client->weapon_sound = 0;
+		gi.sound(ent, CHAN_AUTO, gi.soundindex("weapons/chngnd1a.wav"), 1, ATTN_IDLE, 0);
+	}
+	else
+	{
+		ent->client->weapon_sound = gi.soundindex("weapons/chngnl1a.wav");
+	}
+
+	ent->client->anim_priority = ANIM_ATTACK;
+	if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
+	{
+		ent->s.frame = FRAME_crattak1 - (ent->client->ps.gunframe & 1);
+		ent->client->anim_end = FRAME_crattak9;
+	}
+	else
+	{
+		ent->s.frame = FRAME_attack1 - (ent->client->ps.gunframe & 1);
+		ent->client->anim_end = FRAME_attack8;
+	}
+
+	if (ent->client->ps.gunframe <= 9)
+		shots = 1;
+	else if (ent->client->ps.gunframe <= 14)
+	{
+		if (ent->client->buttons & BUTTON_ATTACK)
+			shots = 2;
+		else
+			shots = 1;
+	}
+	else
+		shots = 3;
+
+	if (ent->client->pers.inventory[ent->client->ammo_index] < shots)
+		shots = ent->client->pers.inventory[ent->client->ammo_index];
+
+	if (!shots)
+	{
+		if (level.time >= ent->pain_debounce_time)
+		{
+			gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+			ent->pain_debounce_time = level.time + 1;
+		}
+		NoAmmoWeaponChange (ent);
+		return;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	for (i=0 ; i<3 ; i++)
+	{
+		ent->client->kick_origin[i] = crandom() * 0.35;
+		ent->client->kick_angles[i] = crandom() * 0.7;
+	}
+
+	for (i=0 ; i<shots ; i++)
+	{
+		// get start / end positions
+		AngleVectors (ent->client->v_angle, forward, right, up);
+		r = 7 + crandom()*4;
+		u = crandom()*4;
+		VectorSet(offset, 0, r, u + ent->viewheight-8);
+		P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+		fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
+	}
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte ((MZ_CHAINGUN1 + shots - 1) | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= shots;
+}
+
+
+void Weapon_Chaingun (edict_t *ent)
+{
+	static int	pause_frames[]	= {38, 43, 51, 61, 0};
+	static int	fire_frames[]	= {5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 0};
+
+	Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 4, 31, 61, 64, pause_frames, fire_frames, Chaingun_Fire);
+
+}
+
+
+/*
+======================================================================
+
+SHOTGUN / SUPERSHOTGUN
+
+======================================================================
+*/
+
+void weapon_shotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage = 4;
+	int			kick = 8;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	if (deathmatch->value)
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
+	else
+		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_SHOTGUN_COUNT, MOD_SHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+void Weapon_Shotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {22, 28, 34, 0};
+	static int	fire_frames[]	= {8, 9, 0};
+
+	Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 7, 18, 36, 39, pause_frames, fire_frames, weapon_shotgun_fire);
+}
+
+
+void weapon_supershotgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	vec3_t		v;
+	int			damage = 6;
+	int			kick = 12;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	v[PITCH] = ent->client->v_angle[PITCH];
+	v[YAW]   = ent->client->v_angle[YAW] - 5;
+	v[ROLL]  = ent->client->v_angle[ROLL];
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+	v[YAW]   = ent->client->v_angle[YAW] + 5;
+	AngleVectors (v, forward, NULL, NULL);
+	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_SSHOTGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 2;
+}
+
+void Weapon_SuperShotgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {29, 42, 57, 0};
+	static int	fire_frames[]	= {7, 0};
+
+	Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 6, 17, 57, 61, pause_frames, fire_frames, weapon_supershotgun_fire);
+}
+
+
+
+/*
+======================================================================
+
+RAILGUN
+
+======================================================================
+*/
+
+void weapon_railgun_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right;
+	vec3_t		offset;
+	int			damage;
+	int			kick;
+
+	if (deathmatch->value)
+	{	// normal damage is too extreme in dm
+		damage = 100;
+		kick = 200;
+	}
+	else
+	{
+		damage = 150;
+		kick = 250;
+	}
+
+	if (is_quad)
+	{
+		damage *= 4;
+		kick *= 4;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -3, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -3;
+
+	VectorSet(offset, 0, 7,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_rail (ent, start, forward, damage, kick);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent-g_edicts);
+	gi.WriteByte (MZ_RAILGUN | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index]--;
+}
+
+
+void Weapon_Railgun (edict_t *ent)
+{
+	static int	pause_frames[]	= {56, 0};
+	static int	fire_frames[]	= {4, 0};
+
+	Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire);
+
+	
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 3, 18, 56, 61, pause_frames, fire_frames, weapon_railgun_fire);
+}
+
+
+/*
+======================================================================
+
+BFG10K
+
+======================================================================
+*/
+
+void weapon_bfg_fire (edict_t *ent)
+{
+	vec3_t	offset, start;
+	vec3_t	forward, right;
+	int		damage;
+	float	damage_radius = 1000;
+
+	if (deathmatch->value)
+		damage = 200;
+	else
+		damage = 500;
+
+	if (ent->client->ps.gunframe == 9)
+	{
+		// send muzzle flash
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_BFG | is_silenced);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+		ent->client->ps.gunframe++;
+
+		PlayerNoise(ent, start, PNOISE_WEAPON);
+		return;
+	}
+
+	// cells can go down during windup (from power armor hits), so
+	// check again and abort firing if we don't have enough now
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 50)
+	{
+		ent->client->ps.gunframe++;
+		return;
+	}
+
+	if (is_quad)
+		damage *= 4;
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+
+	// make a big pitch kick with an inverse fall
+	ent->client->v_dmg_pitch = -40;
+	ent->client->v_dmg_roll = crandom()*8;
+	ent->client->v_dmg_time = level.time + DAMAGE_TIME;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+	fire_bfg (ent, start, forward, damage, 400, damage_radius);
+
+	ent->client->ps.gunframe++;
+
+	PlayerNoise(ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= 50;
+}
+
+void Weapon_BFG (edict_t *ent)
+{
+	static int	pause_frames[]	= {39, 45, 50, 55, 0};
+	static int	fire_frames[]	= {9, 17, 0};
+
+	Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire);
+
+	// RAFAEL
+	if (is_quadfire)
+		Weapon_Generic (ent, 8, 32, 55, 58, pause_frames, fire_frames, weapon_bfg_fire);
+}
+
+
+//======================================================================
+
+
+// RAFAEL
+/*
+	RipperGun
+*/
+
+void weapon_ionripper_fire (edict_t *ent)
+{
+	vec3_t	start;
+	vec3_t	forward, right;
+	vec3_t	offset;
+	vec3_t	tempang;
+	int		damage;
+	//int		kick;
+
+	if (deathmatch->value)
+	{
+		// tone down for deathmatch
+		damage = 30;
+		//kick = 40;
+	}
+	else
+	{
+		damage = 50;
+		//kick = 60;
+	}
+	
+	if (is_quad)
+	{
+		damage *= 4;
+		//kick *= 4;
+	}
+
+	VectorCopy (ent->client->v_angle, tempang);
+	tempang[YAW] += crandom();
+
+	AngleVectors (tempang, forward, right, NULL);
+	
+	VectorScale (forward, -3, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -3;
+
+	// VectorSet (offset, 0, 7, ent->viewheight - 8);
+	VectorSet (offset, 16, 7, ent->viewheight - 8);
+
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	fire_ionripper (ent, start, forward, damage, 500, EF_IONRIPPER);
+
+	// send muzzle flash
+	gi.WriteByte (svc_muzzleflash);
+	gi.WriteShort (ent - g_edicts);
+	gi.WriteByte (MZ_IONRIPPER | is_silenced);
+	gi.multicast (ent->s.origin, MULTICAST_PVS);
+
+	ent->client->ps.gunframe++;
+	PlayerNoise (ent, start, PNOISE_WEAPON);
+
+	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+		ent->client->pers.inventory[ent->client->ammo_index] -= ent->client->pers.weapon->quantity;
+	
+	if (ent->client->pers.inventory[ent->client->ammo_index] < 0)
+		ent->client->pers.inventory[ent->client->ammo_index] = 0;
+}
+
+
+void Weapon_Ionripper (edict_t *ent)
+{
+	static int pause_frames[] = {36, 0};
+	static int fire_frames[] = {5, 0};
+
+	Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire);
+
+	if (is_quadfire)
+		Weapon_Generic (ent, 4, 6, 36, 39, pause_frames, fire_frames, weapon_ionripper_fire);
+}
+
+
+// 
+//	Phalanx
+//
+
+void weapon_phalanx_fire (edict_t *ent)
+{
+	vec3_t		start;
+	vec3_t		forward, right, up;
+	vec3_t		offset;
+	vec3_t		v;
+	//int			kick = 12;
+	int			damage;
+	float		damage_radius;
+	int			radius_damage;
+
+	damage = 70 + (int)(qrandom() * 10.0);
+	radius_damage = 120;
+	damage_radius = 120;
+	
+	if (is_quad)
+	{
+		damage *= 4;
+		radius_damage *= 4;
+	}
+
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+
+	VectorScale (forward, -2, ent->client->kick_origin);
+	ent->client->kick_angles[0] = -2;
+
+	VectorSet(offset, 0, 8,  ent->viewheight-8);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	if (ent->client->ps.gunframe == 8)
+	{
+		v[PITCH] = ent->client->v_angle[PITCH];
+		v[YAW]   = ent->client->v_angle[YAW] - 1.5;
+		v[ROLL]  = ent->client->v_angle[ROLL];
+		AngleVectors (v, forward, right, up);
+		
+		radius_damage = 30;
+		damage_radius = 120;
+	
+		fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage);
+
+		if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+			ent->client->pers.inventory[ent->client->ammo_index]--;
+	}
+	else
+	{
+		v[PITCH] = ent->client->v_angle[PITCH];
+		v[YAW]   = ent->client->v_angle[YAW] + 1.5;
+		v[ROLL]  = ent->client->v_angle[ROLL];
+		AngleVectors (v, forward, right, up);
+		fire_plasma (ent, start, forward, damage, 725, damage_radius, radius_damage);
+
+		// send muzzle flash
+		gi.WriteByte (svc_muzzleflash);
+		gi.WriteShort (ent-g_edicts);
+		gi.WriteByte (MZ_PHALANX | is_silenced);
+		gi.multicast (ent->s.origin, MULTICAST_PVS);
+		
+		PlayerNoise(ent, start, PNOISE_WEAPON);
+	}
+	
+	ent->client->ps.gunframe++;
+	
+}
+
+void Weapon_Phalanx (edict_t *ent)
+{
+	static int	pause_frames[]	= {29, 42, 55, 0};
+	static int	fire_frames[]	= {7, 8, 0};
+
+	Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire);
+
+	if (is_quadfire)
+		Weapon_Generic (ent, 5, 20, 58, 63, pause_frames, fire_frames, weapon_phalanx_fire);
+	
+}
+
+/*
+======================================================================
+
+TRAP
+
+======================================================================
+*/
+
+#define TRAP_TIMER			5.0
+#define TRAP_MINSPEED		300
+#define TRAP_MAXSPEED		700
+
+void weapon_trap_fire (edict_t *ent, qboolean held)
+{
+	vec3_t	offset;
+	vec3_t	forward, right;
+	vec3_t	start;
+	int		damage = 125;
+	float	timer;
+	int		speed;
+	float	radius;
+
+	radius = damage+40;
+	if (is_quad)
+		damage *= 4;
+
+	VectorSet(offset, 8, 8, ent->viewheight-8);
+	AngleVectors (ent->client->v_angle, forward, right, NULL);
+	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
+
+	timer = ent->client->grenade_time - level.time;
+	speed = GRENADE_MINSPEED + (GRENADE_TIMER - timer) * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER);
+	// fire_grenade2 (ent, start, forward, damage, speed, timer, radius, held);
+	fire_trap (ent, start, forward, damage, speed, timer, radius, held);
+	
+// you don't get infinite traps!  ZOID
+//	if (! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
+
+	ent->client->pers.inventory[ent->client->ammo_index]--;
+
+	ent->client->grenade_time = level.time + 1.0;
+}
+
+void Weapon_Trap (edict_t *ent)
+{
+	if ((ent->client->newweapon) && (ent->client->weaponstate == WEAPON_READY))
+	{
+		ChangeWeapon (ent);
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_ACTIVATING)
+	{
+		ent->client->weaponstate = WEAPON_READY;
+		ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_READY)
+	{
+		if ( ((ent->client->latched_buttons|ent->client->buttons) & BUTTON_ATTACK) )
+		{
+			ent->client->latched_buttons &= ~BUTTON_ATTACK;
+			if (ent->client->pers.inventory[ent->client->ammo_index])
+			{
+				ent->client->ps.gunframe = 1;
+				ent->client->weaponstate = WEAPON_FIRING;
+				ent->client->grenade_time = 0;
+			}
+			else
+			{
+				if (level.time >= ent->pain_debounce_time)
+				{
+					gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/noammo.wav"), 1, ATTN_NORM, 0);
+					ent->pain_debounce_time = level.time + 1;
+				}
+				NoAmmoWeaponChange (ent);
+			}
+			return;
+		}
+
+		if ((ent->client->ps.gunframe == 29) || (ent->client->ps.gunframe == 34) || (ent->client->ps.gunframe == 39) || (ent->client->ps.gunframe == 48))
+		{
+			if (rand()&15)
+				return;
+		}
+
+		if (++ent->client->ps.gunframe > 48)
+			ent->client->ps.gunframe = 16;
+		return;
+	}
+
+	if (ent->client->weaponstate == WEAPON_FIRING)
+	{
+		if (ent->client->ps.gunframe == 5)
+			// RAFAEL 16-APR-98
+			// gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/hgrena1b.wav"), 1, ATTN_NORM, 0);
+			gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/trapcock.wav"), 1, ATTN_NORM, 0);
+			// END 16-APR-98
+
+		if (ent->client->ps.gunframe == 11)
+		{
+			if (!ent->client->grenade_time)
+			{
+				ent->client->grenade_time = level.time + GRENADE_TIMER + 0.2;
+				// RAFAEL 16-APR-98
+				ent->client->weapon_sound = gi.soundindex("weapons/traploop.wav");
+				// END 16-APR-98
+			}
+
+			// they waited too long, detonate it in their hand
+			if (!ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
+			{
+				ent->client->weapon_sound = 0;
+				weapon_trap_fire (ent, true);
+				ent->client->grenade_blew_up = true;
+			}
+
+			if (ent->client->buttons & BUTTON_ATTACK)
+				return;
+
+			if (ent->client->grenade_blew_up)
+			{
+				if (level.time >= ent->client->grenade_time)
+				{
+					ent->client->ps.gunframe = 15;
+					ent->client->grenade_blew_up = false;
+				}
+				else
+				{
+					return;
+				}
+			}
+		}
+
+		if (ent->client->ps.gunframe == 12)
+		{
+			ent->client->weapon_sound = 0;
+			weapon_trap_fire (ent, false);
+			if (ent->client->pers.inventory[ent->client->ammo_index] == 0)
+				NoAmmoWeaponChange (ent);
+		}
+
+		if ((ent->client->ps.gunframe == 15) && (level.time < ent->client->grenade_time))
+			return;
+
+		ent->client->ps.gunframe++;
+
+		if (ent->client->ps.gunframe == 16)
+		{
+			ent->client->grenade_time = 0;
+			ent->client->weaponstate = WEAPON_READY;
+		}
+	}
+}
--- /dev/null
+++ b/xatrix/q_shared.c
@@ -1,0 +1,1088 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "../dat.h"
+#include "../fns.h"
+
+#define DEG2RAD( a ) ( a * M_PI ) / 180.0F
+
+vec3_t vec3_origin = {0,0,0};
+
+//============================================================================
+
+#ifdef _WIN32
+#pragma optimize( "", off )
+#endif
+
+void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees )
+{
+	float	m[3][3];
+	float	im[3][3];
+	float	zrot[3][3];
+	float	tmpmat[3][3];
+	float	rot[3][3];
+	int	i;
+	vec3_t vr, vup, vf;
+
+	vf[0] = dir[0];
+	vf[1] = dir[1];
+	vf[2] = dir[2];
+
+	PerpendicularVector( vr, dir );
+	CrossProduct( vr, vf, vup );
+
+	m[0][0] = vr[0];
+	m[1][0] = vr[1];
+	m[2][0] = vr[2];
+
+	m[0][1] = vup[0];
+	m[1][1] = vup[1];
+	m[2][1] = vup[2];
+
+	m[0][2] = vf[0];
+	m[1][2] = vf[1];
+	m[2][2] = vf[2];
+
+	memcpy( im, m, sizeof( im ) );
+
+	im[0][1] = m[1][0];
+	im[0][2] = m[2][0];
+	im[1][0] = m[0][1];
+	im[1][2] = m[2][1];
+	im[2][0] = m[0][2];
+	im[2][1] = m[1][2];
+
+	memset( zrot, 0, sizeof( zrot ) );
+	zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;
+
+	zrot[0][0] = cos( DEG2RAD( degrees ) );
+	zrot[0][1] = sin( DEG2RAD( degrees ) );
+	zrot[1][0] = -sin( DEG2RAD( degrees ) );
+	zrot[1][1] = cos( DEG2RAD( degrees ) );
+
+	R_ConcatRotations( m, zrot, tmpmat );
+	R_ConcatRotations( tmpmat, im, rot );
+
+	for ( i = 0; i < 3; i++ )
+	{
+		dst[i] = rot[i][0] * point[0] + rot[i][1] * point[1] + rot[i][2] * point[2];
+	}
+}
+
+#ifdef _WIN32
+#pragma optimize( "", on )
+#endif
+
+
+
+void AngleVectors (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;
+	}
+}
+
+
+void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal )
+{
+	float d;
+	vec3_t n;
+	float inv_denom;
+
+	inv_denom = 1.0F / DotProduct( normal, normal );
+
+	d = DotProduct( normal, p ) * inv_denom;
+
+	n[0] = normal[0] * inv_denom;
+	n[1] = normal[1] * inv_denom;
+	n[2] = normal[2] * inv_denom;
+
+	dst[0] = p[0] - d * n[0];
+	dst[1] = p[1] - d * n[1];
+	dst[2] = p[2] - d * n[2];
+}
+
+/*
+** assumes "src" is normalized
+*/
+void PerpendicularVector( vec3_t dst, const vec3_t src )
+{
+	int	pos;
+	int i;
+	float minelem = 1.0F;
+	vec3_t tempvec;
+
+	/*
+	** find the smallest magnitude axially aligned vector
+	*/
+	for ( pos = 0, i = 0; i < 3; i++ )
+	{
+		if ( fabs( src[i] ) < minelem )
+		{
+			pos = i;
+			minelem = fabs( src[i] );
+		}
+	}
+	tempvec[0] = tempvec[1] = tempvec[2] = 0.0F;
+	tempvec[pos] = 1.0F;
+
+	/*
+	** project the point onto the plane defined by src
+	*/
+	ProjectPointOnPlane( dst, tempvec, src );
+
+	/*
+	** normalize the result
+	*/
+	VectorNormalize( dst );
+}
+
+
+
+/*
+================
+R_ConcatRotations
+================
+*/
+void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+}
+
+
+/*
+================
+R_ConcatTransforms
+================
+*/
+void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4])
+{
+	out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
+				in1[0][2] * in2[2][0];
+	out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
+				in1[0][2] * in2[2][1];
+	out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
+				in1[0][2] * in2[2][2];
+	out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
+				in1[0][2] * in2[2][3] + in1[0][3];
+	out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
+				in1[1][2] * in2[2][0];
+	out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
+				in1[1][2] * in2[2][1];
+	out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
+				in1[1][2] * in2[2][2];
+	out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
+				in1[1][2] * in2[2][3] + in1[1][3];
+	out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
+				in1[2][2] * in2[2][0];
+	out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
+				in1[2][2] * in2[2][1];
+	out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
+				in1[2][2] * in2[2][2];
+	out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
+				in1[2][2] * in2[2][3] + in1[2][3];
+}
+
+
+//============================================================================
+
+
+
+/*
+===============
+LerpAngle
+
+===============
+*/
+float LerpAngle (float a2, float a1, float frac)
+{
+	if (a1 - a2 > 180)
+		a1 -= 360;
+	if (a1 - a2 < -180)
+		a1 += 360;
+	return a2 + frac * (a1 - a2);
+}
+
+
+float	anglemod(float a)
+{
+/*
+	if (a >= 0)
+		a -= 360*(int)(a/360);
+	else
+		a += 360*( 1 + (int)(-a/360) );
+*/
+	a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535);
+	return a;
+}
+
+	int		i;
+	vec3_t	corners[2];
+
+
+// this is the slow, general version
+int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, cplane_t *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;
+}
+
+/*
+==================
+BoxOnPlaneSide
+
+Returns 1, 2, or 1 + 2
+==================
+*/
+int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, cplane_t *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
+		assert( 0 );
+		break;
+	}
+
+	sides = 0;
+	if (dist1 >= p->dist)
+		sides = 1;
+	if (dist2 < p->dist)
+		sides |= 2;
+
+	assert( sides != 0 );
+
+	return sides;
+}
+
+void ClearBounds (vec3_t mins, vec3_t maxs)
+{
+	mins[0] = mins[1] = mins[2] = 99999;
+	maxs[0] = maxs[1] = maxs[2] = -99999;
+}
+
+void AddPointToBounds (vec3_t v, vec3_t mins, vec3_t maxs)
+{
+	int		i;
+	vec_t	val;
+
+	for (i=0 ; i<3 ; i++)
+	{
+		val = v[i];
+		if (val < mins[i])
+			mins[i] = val;
+		if (val > maxs[i])
+			maxs[i] = val;
+	}
+}
+
+
+int VectorCompare (vec3_t v1, vec3_t v2)
+{
+	if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2])
+			return 0;
+			
+	return 1;
+}
+
+
+vec_t VectorNormalize (vec3_t v)
+{
+	float	length, ilength;
+
+	length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
+	length = sqrt (length);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		v[0] *= ilength;
+		v[1] *= ilength;
+		v[2] *= ilength;
+	}
+		
+	return length;
+
+}
+
+vec_t VectorNormalize2 (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);		// FIXME
+
+	if (length)
+	{
+		ilength = 1/length;
+		out[0] = v[0]*ilength;
+		out[1] = v[1]*ilength;
+		out[2] = v[2]*ilength;
+	}
+		
+	return length;
+
+}
+
+void VectorMA (vec3_t veca, float scale, vec3_t vecb, vec3_t vecc)
+{
+	vecc[0] = veca[0] + scale*vecb[0];
+	vecc[1] = veca[1] + scale*vecb[1];
+	vecc[2] = veca[2] + scale*vecb[2];
+}
+
+
+vec_t _DotProduct (vec3_t v1, vec3_t v2)
+{
+	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
+}
+
+void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]-vecb[0];
+	out[1] = veca[1]-vecb[1];
+	out[2] = veca[2]-vecb[2];
+}
+
+void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out)
+{
+	out[0] = veca[0]+vecb[0];
+	out[1] = veca[1]+vecb[1];
+	out[2] = veca[2]+vecb[2];
+}
+
+void _VectorCopy (vec3_t in, vec3_t out)
+{
+	out[0] = in[0];
+	out[1] = in[1];
+	out[2] = in[2];
+}
+
+void CrossProduct (vec3_t v1, vec3_t v2, vec3_t cross)
+{
+	cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
+	cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
+	cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
+}
+
+double sqrt(double x);
+
+vec_t VectorLength(vec3_t v)
+{
+	int		i;
+	float	length;
+	
+	length = 0;
+	for (i=0 ; i< 3 ; i++)
+		length += v[i]*v[i];
+	length = sqrt (length);		// FIXME
+
+	return length;
+}
+
+void VectorInverse (vec3_t v)
+{
+	v[0] = -v[0];
+	v[1] = -v[1];
+	v[2] = -v[2];
+}
+
+void VectorScale (vec3_t in, vec_t scale, vec3_t out)
+{
+	out[0] = in[0]*scale;
+	out[1] = in[1]*scale;
+	out[2] = in[2]*scale;
+}
+
+//====================================================================================
+
+/*
+============
+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 (char *in, char *out)
+{
+	while (*in && *in != '.')
+		*out++ = *in++;
+	*out = 0;
+}
+
+/*
+============
+COM_FileExtension
+============
+*/
+char *COM_FileExtension (char *in)
+{
+	static char exten[8];
+	int		i;
+
+	while (*in && *in != '.')
+		in++;
+	if (!*in)
+		return "";
+	in++;
+	for (i=0 ; i<7 && *in ; i++,in++)
+		exten[i] = *in;
+	exten[i] = 0;
+	return exten;
+}
+
+/*
+============
+COM_FileBase
+============
+*/
+void COM_FileBase (char *in, char *out)
+{
+	char *s, *s2;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '.')
+		s--;
+	
+	for (s2 = s ; s2 != in && *s2 != '/' ; s2--)
+	;
+	
+	if (s-s2 < 2)
+		out[0] = 0;
+	else
+	{
+		s--;
+		strncpy (out,s2+1, s-s2);
+		out[s-s2] = 0;
+	}
+}
+
+/*
+============
+COM_FilePath
+
+Returns the path up to, but not including the last /
+============
+*/
+void COM_FilePath (char *in, char *out)
+{
+	char *s;
+	
+	s = in + strlen(in) - 1;
+	
+	while (s != in && *s != '/')
+		s--;
+
+	strncpy (out,in, s-in);
+	out[s-in] = 0;
+}
+
+
+/*
+==================
+COM_DefaultExtension
+==================
+*/
+void COM_DefaultExtension (char *path, char *extension)
+{
+	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--;
+	}
+
+	strcat (path, extension);
+}
+
+/*
+============================================================================
+
+					BYTE ORDER FUNCTIONS
+
+============================================================================
+*/
+
+qboolean	bigendien;
+
+// can't just use function pointers, or dll linkage can
+// mess up when qcommon is included in multiple places
+short	(*_BigShort) (short l);
+short	(*_LittleShort) (short l);
+int		(*_BigLong) (int l);
+int		(*_LittleLong) (int l);
+float	(*_BigFloat) (float l);
+float	(*_LittleFloat) (float l);
+
+short	BigShort(short l){return _BigShort(l);}
+short	LittleShort(short l) {return _LittleShort(l);}
+int		BigLong (int l) {return _BigLong(l);}
+int		LittleLong (int l) {return _LittleLong(l);}
+float	BigFloat (float l) {return _BigFloat(l);}
+float	LittleFloat (float l) {return _LittleFloat(l);}
+
+short   ShortSwap (short l)
+{
+	byte    b1,b2;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+
+	return (b1<<8) + b2;
+}
+
+short	ShortNoSwap (short l)
+{
+	return l;
+}
+
+int    LongSwap (int l)
+{
+	byte    b1,b2,b3,b4;
+
+	b1 = l&255;
+	b2 = (l>>8)&255;
+	b3 = (l>>16)&255;
+	b4 = (l>>24)&255;
+
+	return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
+}
+
+int	LongNoSwap (int l)
+{
+	return l;
+}
+
+float FloatSwap (float f)
+{
+	union
+	{
+		float	f;
+		byte	b[4];
+	} dat1, dat2;
+	
+	
+	dat1.f = f;
+	dat2.b[0] = dat1.b[3];
+	dat2.b[1] = dat1.b[2];
+	dat2.b[2] = dat1.b[1];
+	dat2.b[3] = dat1.b[0];
+	return dat2.f;
+}
+
+float FloatNoSwap (float f)
+{
+	return f;
+}
+
+/*
+================
+Swap_Init
+================
+*/
+void Swap_Init (void)
+{
+	byte	swaptest[2] = {1,0};
+
+// set the byte swapping variables in a portable manner	
+	if ( *(short *)swaptest == 1)
+	{
+		bigendien = false;
+		_BigShort = ShortSwap;
+		_LittleShort = ShortNoSwap;
+		_BigLong = LongSwap;
+		_LittleLong = LongNoSwap;
+		_BigFloat = FloatSwap;
+		_LittleFloat = FloatNoSwap;
+	}
+	else
+	{
+		bigendien = true;
+		_BigShort = ShortNoSwap;
+		_LittleShort = ShortSwap;
+		_BigLong = LongNoSwap;
+		_LittleLong = LongSwap;
+		_BigFloat = FloatNoSwap;
+		_LittleFloat = FloatSwap;
+	}
+
+}
+
+
+
+/*
+============
+va
+
+does a varargs printf into a temp buffer, so I don't need to have
+varargs versions of all text functions.
+FIXME: make this buffer size safe someday
+============
+*/
+char	*va(char *format, ...)
+{
+	va_list		argptr;
+	static char		string[1024];
+	
+	va_start (argptr, format);
+	vsprintf (string, format,argptr);
+	va_end (argptr);
+
+	return string;	
+}
+
+
+char	com_token[MAX_TOKEN_CHARS];
+
+/*
+==============
+COM_Parse
+
+Parse a token out of a string
+==============
+*/
+char *COM_Parse (char **data_p)
+{
+	int		c;
+	int		len;
+	char	*data;
+
+	data = *data_p;
+	len = 0;
+	com_token[0] = 0;
+	
+	if (!data)
+	{
+		*data_p = NULL;
+		return "";
+	}
+		
+// skip whitespace
+skipwhite:
+	while ( (c = *data) <= ' ')
+	{
+		if (c == 0)
+		{
+			*data_p = NULL;
+			return "";
+		}
+		data++;
+	}
+	
+// skip // comments
+	if (c=='/' && data[1] == '/')
+	{
+		while (*data && *data != '\n')
+			data++;
+		goto skipwhite;
+	}
+
+// handle quoted strings specially
+	if (c == '\"')
+	{
+		data++;
+		while (1)
+		{
+			c = *data++;
+			if (c=='\"' || !c)
+			{
+				com_token[len] = 0;
+				*data_p = data;
+				return com_token;
+			}
+			if (len < MAX_TOKEN_CHARS)
+			{
+				com_token[len] = c;
+				len++;
+			}
+		}
+	}
+
+// parse a regular word
+	do
+	{
+		if (len < MAX_TOKEN_CHARS)
+		{
+			com_token[len] = c;
+			len++;
+		}
+		data++;
+		c = *data;
+	} while (c>32);
+
+	if (len == MAX_TOKEN_CHARS)
+	{
+//		Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
+		len = 0;
+	}
+	com_token[len] = 0;
+
+	*data_p = data;
+	return com_token;
+}
+
+
+/*
+===============
+Com_PageInMemory
+
+===============
+*/
+int	paged_total;
+
+void Com_PageInMemory (byte *buffer, int size)
+{
+	int		i;
+
+	for (i=size-1 ; i>0 ; i-=4096)
+		paged_total += buffer[i];
+}
+
+
+
+/*
+============================================================================
+
+					LIBRARY REPLACEMENT FUNCTIONS
+
+============================================================================
+*/
+
+void Com_sprintf (char *dest, int size, char *fmt, ...)
+{
+	int		len;
+	va_list		argptr;
+	char	bigbuffer[0x10000];
+
+	va_start (argptr,fmt);
+	len = vsprintf (bigbuffer,fmt,argptr);
+	va_end (argptr);
+	if (len >= size)
+		Com_Printf ("Com_sprintf: overflow of %i in %i\n", len, size);
+	strncpy (dest, bigbuffer, size-1);
+}
+
+/*
+=====================================================================
+
+  INFO STRINGS
+
+=====================================================================
+*/
+
+/*
+===============
+Info_ValueForKey
+
+Searches the string for the given
+key and returns the associated value, or an empty string.
+===============
+*/
+char *Info_ValueForKey (char *s, char *key)
+{
+	char	pkey[512];
+	static	char value[2][512];	// use two buffers so compares
+								// work without stomping on each other
+	static	int	valueindex;
+	char	*o;
+	
+	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)
+		{
+			if (!*s)
+				return "";
+			*o++ = *s++;
+		}
+		*o = 0;
+
+		if (!strcmp (key, pkey) )
+			return value[valueindex];
+
+		if (!*s)
+			return "";
+		s++;
+	}
+}
+
+void Info_RemoveKey (char *s, char *key)
+{
+	char	*start;
+	char	pkey[512];
+	char	value[512];
+	char	*o;
+
+	if (strstr (key, "\\"))
+	{
+//		Com_Printf ("Can't use a key with a \\\n");
+		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 (char *s)
+{
+	if (strstr (s, "\""))
+		return false;
+	if (strstr (s, ";"))
+		return false;
+	return true;
+}
+
+void Info_SetValueForKey (char *s, char *key, char *value)
+{
+	char	newi[MAX_INFO_STRING], *v;
+	int		c;
+	int		maxsize = MAX_INFO_STRING;
+
+	if (strstr (key, "\\") || strstr (value, "\\") )
+	{
+		Com_Printf ("Can't use keys or values with a \\\n");
+		return;
+	}
+
+	if (strstr (key, ";") )
+	{
+		Com_Printf ("Can't use keys or values with a semicolon\n");
+		return;
+	}
+
+	if (strstr (key, "\"") || strstr (value, "\"") )
+	{
+		Com_Printf ("Can't use keys or values with a \"\n");
+		return;
+	}
+
+	if (strlen(key) > MAX_INFO_KEY-1 || strlen(value) > MAX_INFO_KEY-1)
+	{
+		Com_Printf ("Keys and values must be < 64 characters.\n");
+		return;
+	}
+	Info_RemoveKey (s, key);
+	if (!value || !strlen(value))
+		return;
+
+	Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value);
+
+	if (strlen(newi) + strlen(s) > maxsize)
+	{
+		Com_Printf ("Info string length exceeded\n");
+		return;
+	}
+
+	// only copy ascii values
+	s += strlen(s);
+	v = newi;
+	while (*v)
+	{
+		c = *v++;
+		c &= 127;		// strip high bits
+		if (c >= 32 && c < 127)
+			*s++ = c;
+	}
+	*s = 0;
+}
+
+//====================================================================
+
+