commit 930d214707fc01099643d40ccc2757a329418ef3 Author: archive Date: Fri May 24 00:00:00 2002 +0000 as released 2002-05-24 diff --git a/MP_sdk_header4.jpg b/MP_sdk_header4.jpg new file mode 100644 index 0000000..474f705 Binary files /dev/null and b/MP_sdk_header4.jpg differ diff --git a/UK-PC-EULA Level Editor.rtf b/UK-PC-EULA Level Editor.rtf new file mode 100644 index 0000000..6fadb97 --- /dev/null +++ b/UK-PC-EULA Level Editor.rtf @@ -0,0 +1,169 @@ +{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}{\f1\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial{\*\falt Helvetica};} +{\f3\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f86\fnil\fcharset77\fprq0{\*\panose 00000000000000000000}Font8721{\*\falt Times New Roman};}{\f87\froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};} +{\f88\froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};}{\f90\froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}{\f91\froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};} +{\f92\froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};}{\f93\froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}{\f94\froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};} +{\f95\fswiss\fcharset238\fprq2 Arial CE{\*\falt Helvetica};}{\f96\fswiss\fcharset204\fprq2 Arial Cyr{\*\falt Helvetica};}{\f98\fswiss\fcharset161\fprq2 Arial Greek{\*\falt Helvetica};}{\f99\fswiss\fcharset162\fprq2 Arial Tur{\*\falt Helvetica};} +{\f100\fswiss\fcharset177\fprq2 Arial (Hebrew){\*\falt Helvetica};}{\f101\fswiss\fcharset178\fprq2 Arial (Arabic){\*\falt Helvetica};}{\f102\fswiss\fcharset186\fprq2 Arial Baltic{\*\falt Helvetica};}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255; +\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0; +\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\*\cs10 \additive +Default Paragraph Font;}{\s15\ql \li0\ri0\sb72\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 \sbasedon0 \snext15 Body;}{ +\s16\ql \fi-170\li170\ri0\sb72\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin170\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 \sbasedon0 \snext16 Bullet;}{\s17\ql \fi-360\li360\ri0\widctlpar\jclisttab\tx360{\*\pn +\pnlvlbody\ilvl0\ls4\pnrnot0\pndec }\aspalpha\aspnum\faauto\ls4\adjustright\rin0\lin360\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 \sautoupd List Bullet;}{\s18\ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 +\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext18 Body Text;}}{\*\listtable{\list\listtemplateid-682570846\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1\fbias0 \s17\fi-360\li360\jclisttab\tx360 }{\listname ;}\listid-119}{\list\listtemplateid0{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li360\jclisttab\tx360 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li720\jclisttab\tx720 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'02);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li1080\jclisttab\tx1080 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'03);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li1440\jclisttab\tx1440 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'04);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li1800\jclisttab\tx1800 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'03(\'05);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li2160\jclisttab\tx2160 }{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'06.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li2520\jclisttab\tx2520 }{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'07.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li2880\jclisttab\tx2880 }{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'08.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 \fi-360\li3240\jclisttab\tx3240 }{\listname ;}\listid1422678978}}{\*\listoverridetable{\listoverride\listid1422678978\listoverridecount9{\lfolevel\listoverrideformat +{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4 +\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc2\levelnfcn2\leveljc0 +\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0 +\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'03);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1 +\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0 +{\leveltext\'03(\'05);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'06.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'07.;}{\levelnumbers +\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'08.;}{\levelnumbers\'01;}\chbrdr +\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}\ls1}{\listoverride\listid1422678978\listoverridecount9{\lfolevel\listoverrideformat{\listlevel\levelnfc1\levelnfcn1\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'02\'00.;}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc3\levelnfcn3\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'01.;}{\levelnumbers +\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'02.;}{\levelnumbers\'01;}\chbrdr +\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'03);}{\levelnumbers\'01;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'04);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'05);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'06);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc4\levelnfcn4\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'07);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc2\levelnfcn2\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'03(\'08);}{\levelnumbers\'02;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}\ls2}{\listoverride\listid1422678978\listoverridecount9{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'02\'00.;}{\levelnumbers +\'01;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'04\'00.\'01.;}{\levelnumbers\'01\'03;}\chbrdr +\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'06\'00.\'01.\'02.;}{\levelnumbers\'01\'03\'05;}\chbrdr +\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'08\'00.\'01.\'02.\'03.;}{\levelnumbers\'01\'03\'05\'07;} +\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'0a\'00.\'01.\'02.\'03.\'04.;}{\levelnumbers +\'01\'03\'05\'07\'09;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext +\'0c\'00.\'01.\'02.\'03.\'04.\'05.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0 +\levelindent0{\leveltext\'0e\'00.\'01.\'02.\'03.\'04.\'05.\'06.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0 +\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'10\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f;}\chbrdr\brdrnone\brdrcf1 \chshdng0\chcfpat1\chcbpat1 }}{\lfolevel\listoverrideformat{\listlevel +\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'12\'00.\'01.\'02.\'03.\'04.\'05.\'06.\'07.\'08.;}{\levelnumbers\'01\'03\'05\'07\'09\'0b\'0d\'0f\'11;}\chbrdr\brdrnone\brdrcf1 +\chshdng0\chcfpat1\chcbpat1 }}\ls3}{\listoverride\listid-119\listoverridecount0\ls4}}{\info{\title SOFTWARE LICENSE AGREEMENT}{\author Mike Rivera}{\operator Rick Johnson}{\creatim\yr2002\mo5\dy9\hr15\min1}{\revtim\yr2002\mo5\dy9\hr15\min1}{\version2} +{\edmins0}{\nofpages3}{\nofwords1802}{\nofchars10272}{\nofcharsws12614}{\vern8247}}\margl2160\margr2160 \widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin2160 +\dgvorigin1440\dghshow1\dgvshow1\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule \fet0\sectd \linex0\endnhere\sectdefaultcl {\*\pnseclvl1 +\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5 +\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain \s15\ql \li0\ri0\sb72\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 +\f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 SOFTWARE LICENSE AGREEMENT +\par }\pard \s15\ql \li0\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\f1\fs20 +IMPORTANT - READ CAREFULLY: USE OF THIS PROGRAM IS SUBJECT TO THE SOFTWARE LICENSE TERMS SET FORTH BELOW. "PROGRAM" INCLUDES ALL SOFTWARE INCLUDED WITH THIS AGREEMENT, THE ASSOCIATED MEDIA, ANY PRINTED MATERIALS, AND ANY ONLINE O +R ELECTRONIC DOCUMENTATION, AND ANY AND ALL COPIES OF SUCH SOFTWARE AND MATERIALS. BY OPENING THIS PACKAGE, INSTALLING, AND/OR USING THE PROGRAM AND ANY SOFTWARE PROGRAMS INCLUDED WITHIN, YOU ACCEPT THE TERMS OF THIS LICENSE WITH ACTIVISION, INC. ("ACTIVI +SION"). +\par LIMITED USE LICENSE. Subject to the conditions described below, Activision grants you the non-exclusive, non-transferable, limited right and license to install and use one copy of this Program solely and exclusively for your personal use. All right +s not specifically granted under this Agreement are reserved by Activision and, as applicable, Activision\rquote +s licensors. This Program is licensed, not sold, for your use. Your license confers no title or ownership in this Program and should not be construed as a sale of any rights in this Program. +\par +\par LICENSE CONDITIONS. +\par You shall not: +\par }\pard\plain \s16\ql \fi-170\li170\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin170\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 \bullet \tab }{\f1\fs20 +Exploit this Program or any of its parts commercially, including but not limited to use at a cyber cafe, computer gaming center or any other location-based site. Activision may + offer a separate Site License Agreement to permit you to make this Program available for commercial use; see the contact information below. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Use this Program, or permit use of this Program, on more than one computer, computer terminal, or workstation at the same time. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Make copies of this Program or any part thereof, or make copies of the materials accompanying this Program. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Copy this Program onto a hard drive or other storage device; you must run this Program from the included CD-ROM (although this Pr +ogram itself may automatically copy a portion of this Program onto your hard drive during installation in order to run more efficiently). +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Use the program, or permit use of this Program, in a network, multi-user arrangement or remote access arrangement, including any online use, except as otherwise explicitly provided by this Program. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Sell, rent, lease, license, distribute or otherwise transfer this Program, or any copies of this Program, without the express prior written consent of Activision. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Reverse engineer, derive source code, modify, decompile, disassemble, or create derivative works of this Program, in whole or in part. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Remove, disable or circumvent any proprietary notices or labels contained on or within the Program. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 Export or re-export this Program or any copy or adaptation in violation of any applicable laws or regulations of the United Sates government. +\par +\par }\pard\plain \s15\ql \li0\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 +OWNERSHIP. All title, ownership rights and intellectual property rights in and to this Program and any and all copies thereof (including + but not limited to any titles, computer code, themes, objects, characters, character names, stories, dialog, catch phrases, locations, concepts, artwork, animation, sounds, musical compositions, audio-visual effects, methods of operation, moral rights, a +ny related documentation, and "applets" incorporated into this Program) are owned by Activision, affiliates of Activision or Activision\rquote +s licensors. This Program is protected by the copyright laws of the United States, international copyright treaties and conventions and other laws. This Program contains certain licensed materials and Activision\rquote +s licensors may protect their rights in the event of any violation of this Agreement. +\par PROGRAM UTILITIES. This Program contains certain design, programming and proces +sing utilities, tools, assets and other resources ("Program Utilities") for use with this Program that allow you to create customized new game levels and other related game materials for personal use in connection with the Program ("New Game Materials"). +The use of the Program Utilities is subject to the following additional license restrictions: +\par }\pard\plain \s16\ql \fi-170\li170\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin170\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 \bullet \tab }{\f1\fs20 +You agree that, as a condition to your using the Program Utilities, you will not use or allow third parties to use the Program Utilities and the New Game Materia +ls created by you for any commercial purposes, including but not limited to selling, renting, leasing, licensing, distributing, or otherwise transferring the ownership of such New Game Materials, whether on a stand alone basis or packaged in combination w +i +th the New Game Materials created by others, through any and all distribution channels, including, without limitation, retail sales and on-line electronic distribution. You agree not to solicit, initiate or encourage any proposal or offer from any person +or entity to create any New Game Materials for commercial distribution. You agree to promptly inform Activision in writing of any instances of your receipt of any such proposal or offer. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 If you decide to make available the use of the New Game Materials created by you to other gamers, you agree to do so solely without charge. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 New Game Materials shall not contain modifications to any COM, EXE or DLL files or to any other executable Product files. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 New Game Materials may be created only if such New Game Materials can be used exclusively in combination with the retail version of the Program. New Game Materials may not be designed to be used as a stand-alone product. + +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 New Game Materials must not contain any illegal, obscene or defamatory materials, material +s that infringe rights of privacy and publicity of third parties or (without appropriate irrevocable licenses granted specifically for that purpose) any trademarks, copyright-protected works or other properties of third parties. +\par }{\f1\fs20 \bullet \tab }{\f1\fs20 All New Game Materials must contain prominent identification at least in any on-line description and with reasonable duration on the opening screen: (a) the name and E-mail address of the New Game Materials\rquote + creator(s) and (b) the words "THIS MATERIAL IS NOT MADE OR SUPPORTED BY ACTIVISION." +\par +\par }\pard\plain \s15\ql \li0\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 +LIMITED WARRANTY: Activision warrants to the original consumer purchaser of this Program that the recording medium on which the Program is recorded will be free from defects in material and workmanship for 90 days from the date of purchase. If + the recording medium is found defective within 90 days of original purchase, Activision agrees to replace, free of charge, any product discovered to be defective within such period upon its receipt of the Product, postage paid, with proof of the date of +p +urchase, as long as the Program is still being manufactured by Activision. In the event that the Program is no longer available, Activision retains the right to substitute a similar program of equal or greater value. This warranty is limited to the record +i +ng medium containing the Program as originally provided by Activision and is not applicable to normal wear and tear. This warranty shall not be applicable and shall be void if the defect has arisen through abuse, mistreatment, or neglect. Any implied warr +anties prescribed by statute are expressly limited to the 90-day period described above. +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 +EXCEPT AS SET FORTH ABOVE, THIS WARRANTY IS IN LIEU OF ALL OTHER WARRANTIES, WHETHER ORAL OR WRITTEN, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, FIT +NESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, AND NO OTHER REPRESENTATIONS OR CLAIMS OF ANY KIND SHALL BE BINDING ON OR OBLIGATE ACTIVISION. }{\f1\fs20 +WILL ACTIVISION BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGE RESULTING FROM POSSESSION, USE OR M +ALFUNCTION OF THIS PRODUCT, INCLUDING DAMAGE TO PROPERTY AND, TO THE EXTENT PERMITTED BY LAW, DAMAGES FOR PERSONAL INJURY, EVEN IF ACTIVISION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED +W +ARRANTY LASTS AND/OR THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATIONS AND/OR EXCLUSION OR LIMITATION OF LIABILITY MAY NOT APPLY TO YOU. THIS WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS, AND YOU MAY HAVE OTHER RIGHT +S WHICH VARY FROM STATE TO STATE. +\par }\pard\plain \s15\ql \li0\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 +When returning the Program for warranty replacement please send the original product disks only in protective packaging and include: (1) a photocopy of your dated sales receipt; (2) your name and return address typed or cle +arly printed; (3) a brief note describing the defect, the problem(s) you are encountered and the system on which you are running the Program; (4) if you are returning the Program after the 90-day warranty period, but within one year after the date of purc +hase, please include cheque or money order payable to Activision for $10\~U.S. ($19 AUD for Australia, or }{\f1\fs20 \'a310.00}{\f1\fs20 for Europe) currency per CD. Note: Certified mail recommended. +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 In Europe send to: +\par +\par }\pard \ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 {\f1\fs20 WARRANTY REPLACEMENTS}{\f1\fs20\lang2057\langfe1033\langnp2057 +\par }\pard\plain \s18\ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 \fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f1\fs20\lang1033\langfe1033\langnp1033 ACTIVISION (UK) Ltd., Parliament House, +St Laurence Way, Slough, Berkshire, SL1 2BW, United Kingdom. +\par }\pard\plain \ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 Disc Replacement: +44 (0)}{\f1\fs20\cf6 }{\f1\fs20 8705 143 525}{\f1\fs20\lang2057\langfe1033\langnp2057 +\par }\pard \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\f1\fs20 +\par +\par In Australia send to: +\par +\par Warranty Replacements +\par Activision +\par }\pard \ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 {\f1\fs20 Century Plaza}{\f1\fs20 +\par }{\f1\fs20 41 Rawson Street}{\f1\fs20 +\par }{\f1\fs20 Epping, NSW 2121}{\f1\fs20 +\par }{\f1\fs20 Australia}{\f1\fs20 +\par }\pard \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\f1\fs20 +\par }\pard\plain \s15\ql \li0\ri0\sb7\keep\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f86\fs18\lang1024\langfe1024\cgrid\noproof\langnp1033\langfenp1033 {\f1\fs20 LIMITATION ON DAMAGES: IN NO EVENT WILL + ACTIVISION BE LIABLE FOR SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES RESULTING FROM POSSESSION, USE OR MALFUNCTION OF THE PROGRAM, INCLUDING DAMAGES TO PROPERTY, LOSS OF GOODWILL, COMPUTER FAILURE OR MALFUNCTION AND, TO THE EXTENT PERMITTED BY LAW, DAMA +GES FOR PERSONAL INJURIES, EVEN IF ACTIVISION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ACTIVISION\rquote +S LIABILITY SHALL NOT EXCEED THE ACTUAL PRICE PAID FOR THE LICENSE TO USE THIS PROGRAM. SOME STATES/COUNTRIES DO NOT ALLOW LIMITATIONS ON HOW LONG +AN IMPLIED WARRANTY LASTS AND/OR THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATIONS AND/OR EXCLUSION OR LIMITATION OF LIABILITY MAY NOT APPLY TO YOU. THIS WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS, AND YOU MAY HAVE + OTHER RIGHTS WHICH VARY FROM JURISDICTION TO JURISDICTION. +\par +\par TERMINATION: Without prejudice to any other rights of Activision, this Agreement will terminate automatically if you fail to comply with its terms and conditions. In such event, you must destroy all copies of this Program and all of its component parts. + +\par U.S. GOVERNMENT RESTRICTED RIGHTS: The Program and documentation have been developed entirely at private expense and are provided as "Commercial Computer Software" or "restricted computer software." + Use, duplication or disclosure by the U.S. Government or a U.S. Government subcontractor is subject to the restrictions set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clauses in DFARS 252.227-7013 or as set for +th in subparagraph (c)(1) and (2) of the Commercial Computer Software Restricted Rights clauses at FAR 52.227-19, as applicable. The Contractor/Manufacturer is Activision, Inc., 3100 Ocean Park Boulevard, Santa Monica, California 90405. +\par INJUNCTION: Because +Activision would be irreparably damaged if the terms of this Agreement were not specifically enforced, you agree that Activision shall be entitled, without bond, other security or proof of damages, to appropriate equitable remedies with respect to breache +s of this Agreement, in addition to such other remedies as Activision may otherwise have under applicable laws. +\par +\par INDEMNITY: You agree to indemnify, defend and hold Activision, its partners, affiliates, licensors, contractors, officers, directors, employees +and agents harmless from all damages, losses and expenses arising directly or indirectly from your acts and omissions to act in using the Product pursuant to the terms of this Agreement. +\par +\par MISCELLANEOUS: This Agreement represents the complete agreement conc +erning this license between the parties and supersedes all prior agreements and representations between them. It may be amended only by a writing executed by both parties. If any provision of this Agreement is held to be unenforceable for any reason, such + +provision shall be reformed only to the extent necessary to make it enforceable and the remaining provisions of this Agreement shall not be affected. This Agreement shall be construed under California law as such law is applied to agreements between Calif +ornia residents entered into and to be performed within California, except as governed by federal law and you consent to the exclusive jurisdiction of the state and federal courts in Los Angeles, California. +\par +\par If you have any questions concerning this license, you may contact Activision at 3100 Ocean Park Boulevard, Santa Monica, California 90405,}{\f1\fs20 USA,}{\f1\fs20 (310) 255-2000, Attn. Business and Legal Affairs, legal@activision.com}{\f1\fs20 .}{ +\f1\fs20 +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 +\par }} \ No newline at end of file diff --git a/US-PC-EULA Level-Editor.rtf b/US-PC-EULA Level-Editor.rtf new file mode 100644 index 0000000..e8a3cdf --- /dev/null +++ b/US-PC-EULA Level-Editor.rtf @@ -0,0 +1,140 @@ +{\rtf1\ansi\ansicpg1252\uc1 \deff0\deflang1033\deflangfe1033{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman{\*\falt Times New Roman};}{\f1\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial{\*\falt Helvetica};} +{\f12\froman\fcharset0\fprq2{\*\panose 00000000000000000000}New York{\*\falt Times New Roman};}{\f27\fswiss\fcharset0\fprq2{\*\panose 020b0604030504040204}Tahoma;}{\f38\fswiss\fcharset0\fprq2{\*\panose 020b0603020202020204}Trebuchet MS;} +{\f85\fnil\fcharset77\fprq0{\*\panose 00000000000000000000}Font14579{\*\falt Times New Roman};}{\f86\froman\fcharset238\fprq2 Times New Roman CE{\*\falt Times New Roman};}{\f87\froman\fcharset204\fprq2 Times New Roman Cyr{\*\falt Times New Roman};} +{\f89\froman\fcharset161\fprq2 Times New Roman Greek{\*\falt Times New Roman};}{\f90\froman\fcharset162\fprq2 Times New Roman Tur{\*\falt Times New Roman};}{\f91\froman\fcharset177\fprq2 Times New Roman (Hebrew){\*\falt Times New Roman};} +{\f92\froman\fcharset178\fprq2 Times New Roman (Arabic){\*\falt Times New Roman};}{\f93\froman\fcharset186\fprq2 Times New Roman Baltic{\*\falt Times New Roman};}{\f94\fswiss\fcharset238\fprq2 Arial CE{\*\falt Helvetica};} +{\f95\fswiss\fcharset204\fprq2 Arial Cyr{\*\falt Helvetica};}{\f97\fswiss\fcharset161\fprq2 Arial Greek{\*\falt Helvetica};}{\f98\fswiss\fcharset162\fprq2 Arial Tur{\*\falt Helvetica};}{\f99\fswiss\fcharset177\fprq2 Arial (Hebrew){\*\falt Helvetica};} +{\f100\fswiss\fcharset178\fprq2 Arial (Arabic){\*\falt Helvetica};}{\f101\fswiss\fcharset186\fprq2 Arial Baltic{\*\falt Helvetica};}{\f302\fswiss\fcharset238\fprq2 Tahoma CE;}{\f303\fswiss\fcharset204\fprq2 Tahoma Cyr;} +{\f305\fswiss\fcharset161\fprq2 Tahoma Greek;}{\f306\fswiss\fcharset162\fprq2 Tahoma Tur;}{\f307\fswiss\fcharset177\fprq2 Tahoma (Hebrew);}{\f308\fswiss\fcharset178\fprq2 Tahoma (Arabic);}{\f309\fswiss\fcharset186\fprq2 Tahoma Baltic;} +{\f390\fswiss\fcharset238\fprq2 Trebuchet MS CE;}{\f394\fswiss\fcharset162\fprq2 Trebuchet MS Tur;}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0; +\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\stylesheet{ +\ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f27\fs24\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 Normal;}{\s1\ql \li0\ri0\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 +\f38\fs48\lang1033\langfe1033\kerning32\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 1;}{\s2\ql \li0\ri0\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f38\fs36\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 +\sbasedon0 \snext0 heading 2;}{\s3\ql \li0\ri0\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f38\fs28\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 3;}{ +\s4\ql \li0\ri0\sb240\sa60\keepn\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f38\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 4;}{ +\s5\ql \li0\ri0\sb240\sa60\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f38\fs20\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 5;}{ +\s6\ql \li0\ri0\sb240\sa60\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f38\fs16\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 heading 6;}{\*\cs10 \additive Default Paragraph Font;}{\*\cs15 \additive \ul\cf0 +\sbasedon10 Hyperlink;}{\*\cs16 \additive \ul\cf0 \sbasedon10 FollowedHyperlink;}{\s17\ql \li0\ri0\sb60\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext17 Body;} +{\s18\ql \li0\ri0\sl480\slmult0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \caps\f85\fs54\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext18 Heading;}{ +\s19\ql \fi-145\li288\ri0\sb60\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin288\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \sbasedon0 \snext0 Bullet;}{\s20\ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 +\fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 \sbasedon0 \snext20 Body Text;}}{\info{\title SOFTWARE LICENSE AGREEMENT}{\author Belinda M. Van Sickle}{\operator Rick Johnson}{\creatim\yr2002\mo5\dy9\hr14\min50}{\revtim\yr2002\mo5\dy9\hr14\min50} +{\printim\yr1999\mo8\dy13\hr8\min44}{\version2}{\edmins0}{\nofpages1}{\nofwords1706}{\nofchars9725}{\*\company Ignited Minds, LLC}{\nofcharsws11942}{\vern8247}}{\*\userprops {\propname Microsoft Theme}\proptype30{\staticval klingon-industrial 111}} +\widowctrl\ftnbj\aenddoc\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dghspace180\dgvspace180\dghorigin1701\dgvorigin1984\dghshow0\dgvshow0\jexpand\viewkind4\viewscale100\pgbrdrhead\pgbrdrfoot\htmautsp\nolnhtadjtbl\lnbrkrule +\fet0{\*\background {\shp{\*\shpinst\shpleft0\shptop0\shpright0\shpbottom0\shpfhdr0\shpbxmargin\shpbxignore\shpbymargin\shpbyignore\shpwr0\shpwrk0\shpfblwtxt1\shpz0\shplid1025{\sp{\sn shapeType}{\sv 1}}{\sp{\sn fFlipH}{\sv 0}}{\sp{\sn fFlipV}{\sv 0}} +{\sp{\sn fillType}{\sv 3}}{\sp{\sn fillBlipName}{\sv indtextb}}{\sp{\sn fFilled}{\sv 1}}{\sp{\sn lineWidth}{\sv 0}}{\sp{\sn fLine}{\sv 0}}{\sp{\sn bWMode}{\sv 9}}{\sp{\sn fBackground}{\sv 1}}{\sp{\sn fLayoutInCell}{\sv 1}}}}}\sectd +\linex0\endnhere\pgbrdropt32\sectlinegrid360\sectdefaultcl {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang{\pntxta .}}{\*\pnseclvl4 +\pnlcltr\pnstart1\pnindent720\pnhang{\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (} +{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang{\pntxtb (}{\pntxta )}}\pard\plain \s18\ql \li0\ri0\sl-480\slmult0 +\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \caps\f85\fs54\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\b\f1\fs20 SOFTWARE LICENSE AGREEMENT +\par }\pard\plain \s17\ql \li0\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 IMPORTANT - READ CAREFULLY: YOUR USE OF THIS SOFTWARE (THE \'93PROGRAM\'94 +) IS SUBJECT TO THE SOFTWARE LICENSE TERMS SET FORTH BELOW. THE \'93PROGRAM\'94 INCLUDES ALL SOFTWARE INCLUDED WITH THIS AGREEMENT, THE ASSOCIATED MEDIA, ANY PRINTED M +ATERIALS, AND ANY ON-LINE OR ELECTRONIC DOCUMENTATION, AND ANY AND ALL COPIES OF SUCH SOFTWARE AND MATERIALS. BY OPENING THIS PACKAGE, INSTALLING, AND/OR USING THE PROGRAM AND ANY SOFTWARE PROGRAMS INCLUDED WITHIN THE PROGRAM, YOU ACCEPT THE TERMS OF THIS + LICENSE WITH ACTIVISION, INC. (\'93ACTIVISION\'94). +\par +\par LIMITED USE LICENSE: Subject to the conditions described below, Activision grants you the non-exclusive, non-transferable, limited right and license to install and use one copy of the Program solely and exclu +sively for your personal use. All rights not specifically granted under this Agreement are reserved by Activision and, as applicable, Activision\rquote +s licensors. The Program is licensed, not sold, for your use. Your license confers no title or ownership in the + Program and should not be construed as a sale of any rights in the Program. All rights not specifically granted under this Agreement are reserved by Activision and, as applicable, its licensors. +\par +\par LICENSE CONDITIONS +\par You agree not to: +\par }\pard\plain \s19\ql \fi-113\li119\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin119\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 \bullet \tab Exploit the Program +or any of its parts commercially, including but not limited to use at a cyber cafe, computer gaming center or any other location-based site. Activision may offer a separate Site License Agreement to permit you to make the Program available for commercial +use; see the contact information below. +\par \bullet \tab Sell, rent, lease, license, distribute or otherwise transfer this Program, or any copies of this Program, without the express prior written consent of Activision. +\par \bullet \tab Use the Program, or permit use of the Program, in a network, multi-user arrangement or remote access arrangement, including any on-line use, except as otherwise specifically provided by the Program. +\par \bullet \tab Use the Program, or permit use of the Program, on more than one computer, computer terminal, or workstation at the same time. +\par \bullet \tab Make copies of the Program or any part thereof, except for back up or archival purposes, or make copies of the materials accompanying the Program. +\par \bullet \tab Copy the Program onto a hard drive or other storage device; you must run the Program + from the included CD-ROM (although the Program itself may automatically copy a portion of the Program onto your hard drive during installation in order to run more efficiently). +\par \bullet \tab Reverse engineer, derive source code, modify, decompile, or disassemble the Program, in whole or in part. +\par \bullet \tab Remove, disable or circumvent any proprietary notices or labels contained on or within the Program. +\par \bullet \tab Export or re-export the Program or any copy or adaptation thereof in violation of any applicable laws or regulations. +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f27\fs24\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 { +\par }\pard\plain \s17\ql \li0\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 OW +NERSHIP: All title, ownership rights and intellectual property rights in and to the Program and any and all copies thereof are owned by Activision or its licensors. The Program is protected by the copyright laws of the United States, international copyrig +ht treaties and conventions and other laws. The Program contains certain licensed materials and Activision\rquote +s licensors may protect their rights in the event of any violation of this Agreement. You agree not to remove, disable or circumvent any proprietary notices or labels contained on or within the Program. +\par +\par THE PROGRAM UTILITIES: The Program contains certain design, programming and processing utilities, tools, assets and other resources (\'93the Program Utilities\'94) for use with the Program that allow you to c +reate customized new game levels and other related game materials for personal use in connection with the Program (\'93New Game Materials\'94). The use of the Program Utilities is subject to the following additional license restrictions: +\par }\pard\plain \s19\ql \fi-108\li119\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin119\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 \bullet \tab You agree that, as a c +ondition to your using the Program Utilities, you will not use or allow third parties to use the Program Utilities and the New Game Materials created by you for any commercial purposes, including but not limited to selling, renting, leasing, licensing, di +s +tributing, or otherwise transferring the ownership of such New Game Materials, whether on a stand alone basis or packaged in combination with the New Game Materials created by others, through any and all distribution channels, including, without limitatio +n +, retail sales and on-line electronic distribution. You agree not to solicit, initiate or encourage any proposal or offer from any person or entity to create any New Game Materials for commercial distribution. You agree to promptly inform Activision in wr +iting of any instances of your receipt of any such proposal or offer. +\par \bullet \tab If you decide to make available the use of the New Game Materials created by you to other gamers, you agree to do so solely without charge. +\par \bullet \tab New Game Materials shall not contain modifications to any COM, EXE or DLL files or to any other executable Product files. +\par \bullet \tab New Game Materials may be created only if such New Game Materials can be used exclusively in combination with the retail version of the Program. New Game Materials may not be designed to be used as a stand-alone product. +\par \bullet \tab New Game Materials must not contain any illegal, obscene or defamatory materials, materials that infringe rights of privacy and publicity of third parties or (without appropriate irrevocable licenses granted + specifically for that purpose) any trademarks, copyright-protected works or other properties of third parties. +\par \bullet \tab All New Game Materials must contain prominent identification at least in any on-line description and with reasonable duration on the opening screen: (a) the name and E-mail address of the New Game Materials\rquote + creator(s) and (b) the words \'93THIS MATERIAL IS NOT MADE OR SUPPORTED BY ACTIVISION.\'94 +\par }\pard\plain \s17\ql \li0\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 +\par LIMITED WARRANTY: Activision warrants to the original consumer purchaser of the Program that the record +ing medium on which the Program is recorded will be free from defects in material and workmanship for 90 days from the date of purchase. If the recording medium is found defective within 90 days of original purchase, Activision agrees to replace, free of +c +harge, any product discovered to be defective within such period upon its receipt of the Product, postage paid, with proof of the date of purchase, as long as the Program is still being manufactured by Activision. In the event that the Program is no longe +r + available, Activision retains the right to substitute a similar program of equal or greater value. This warranty is limited to the recording medium containing the Program as originally provided by Activision and is not applicable to normal wear and tear. + This warranty shall not be applicable and shall be void if the defect has arisen through abuse, mistreatment, or neglect. Any implied warranties prescribed by statute are expressly limited to the 90-day period described above.}{\f1\fs20 +\par }{\f1\fs20 EXCEPT AS SET FORTH ABOVE, TH +IS WARRANTY IS IN LIEU OF ALL OTHER WARRANTIES, WHETHER ORAL OR WRITTEN, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, AND NO OTHER REPRESENTATIONS OR CLAIMS OF ANY KIND SHALL BE BINDI +NG ON OR OBLIGATE ACTIVISION. +\par When returning the Program for warranty replacement please send the original product disks only in protective packaging and include: (1) a photocopy of your dated sales receipt; (2) your name and return address typed or clearl +y printed; (3) a brief note describing the defect, the problem(s) you are encountered and the system on which you are running the Program; (4) if you are returning the Program after the 90-day warranty period, but within one year after the date of purchas +e, please include check or money order for $10 U.S. (A$19 for Australia, or \'a310.00 for Europe) currency per CD or floppy disk replacement. Note: Certified mail recommended. +\par In the U.S. send to: +\par +\par Warranty Replacements +\par Activision, Inc. +\par P.O. Box 67713 +\par Los Angeles, California 90067 +\par +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f27\fs24\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\b\f1\fs20 In Europe send to: +\par }{\f1\fs20 +\par WARRANTY REPLACEMENTS +\par }\pard\plain \s20\ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 \fs24\lang2057\langfe1033\cgrid\langnp2057\langfenp1033 {\f1\fs20\lang1033\langfe1033\langnp1033 +ACTIVISION (UK) Ltd., Parliament House, St Laurence Way, Slough, Berkshire, SL1 2BW, United Kingdom. +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f27\fs24\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 Disc Replacement: +44 (0)}{\f1\fs20\cf6 }{\f1\fs20 8705 143 525 +\par +\par }{\b\f1\fs20 In Australia send to: +\par }{\f1\fs20 +\par Warranty Replacements +\par Activision +\par }\pard \ql \li0\ri0\widctlpar\nooverflow\faauto\rin0\lin0\itap0 {\f1\fs20 Century Plaza}{\f1\fs20\cf0 +\par }{\f1\fs20 41 Rawson Street}{\f1\fs20 +\par }{\f1\fs20 Epping, NSW 2121}{\f1\fs20 +\par }{\f1\fs20 Australia +\par }\pard\plain \s17\ql \li0\ri0\sb29\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f12\fs18\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 +\par LIMITATION ON DAMAGES: IN NO EVENT WILL ACTIVISION BE LIABLE FOR SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES RESULTING FROM POSSESSION, USE OR MALFUNCTION OF THE PROGRAM, INCLUDING DAMAGES T +O PROPERTY, LOSS OF GOODWILL, COMPUTER FAILURE OR MALFUNCTION AND, TO THE EXTENT PERMITTED BY LAW, DAMAGES FOR PERSONAL INJURIES, EVEN IF ACTIVISION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ACTIVISION\rquote +S LIABILITY SHALL NOT EXCEED THE ACTUAL PRI +CE PAID FOR THE LICENSE TO USE THIS PROGRAM. SOME STATES/COUNTRIES DO NOT ALLOW LIMITATIONS ON HOW LONG AN IMPLIED WARRANTY LASTS AND/OR THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THE ABOVE LIMITATIONS AND/OR EXCLUSION OR LIMIT +ATION OF LIABILITY MAY NOT APPLY TO YOU. THIS WARRANTY GIVES YOU SPECIFIC LEGAL RIGHTS, AND YOU MAY HAVE OTHER RIGHTS WHICH VARY FROM JURISDICTION TO JURISDICTION. +\par TERMINATION: Without prejudice to any other rights of Activision, this Agreement will terminate automatically if you fail to comply with its terms and conditions. In such event, you must destroy all copies of the Program and all of its component parts. + +\par +\par U.S. GOVERNMENT RESTRICTED RIGHTS: the Program and documentation have been developed entirely at private expense and are provided as \'93Commercial Computer Software\'94 or \'93restricted computer software.\'94 + Use, duplication or disclosure by the U.S. Government or a U.S. Government subcontractor is subject to the restrictions set forth in subparagraph (c)(1) +(ii) of the Rights in Technical Data and Computer Software clauses in DFARS 252.227-7013 or as set forth in subparagraph (c)(1) and (2) of the Commercial Computer Software Restricted Rights clauses at FAR 52.227-19, as applicable. The Contractor/Manufactu +rer is Activision, Inc., 3100 Ocean Park Boulevard, Santa Monica, California 90405. +\par +\par INJUNCTION: Because Activision would be irreparably damaged if the terms of this Agreement were not specifically enforced, you agree that Activision shall be entitled, with +out bond, other security or proof of damages, to appropriate equitable remedies with respect to breaches of this Agreement, in addition to such other remedies as Activision may otherwise have under applicable laws. +\par +\par INDEMNITY: You agree to indemnify, defen +d and hold Activision, its partners, licensors, affiliates, contractors, officers, directors, employees and agents harmless from all damages, losses and expenses arising directly or indirectly from your acts and omissions to act in using the Product pursu +ant to the terms of this Agreement +\par +\par MISCELLANEOUS: This Agreement represents the complete agreement concerning this license between the parties and supersedes all prior agreements and representations between them. It may be amended only by a writing execut +ed by both parties. If any provision of this Agreement is held to be unenforceable for any reason, such provision shall be reformed only to the extent necessary to make it enforceable and the remaining provisions of this Agreement shall not be affected. T +h +is Agreement shall be construed under California law as such law is applied to agreements between California residents entered into and to be performed within California, except as governed by federal law and you consent to the exclusive jurisdiction of t +he state and federal courts in Los Angeles, California. +\par +\par If you have any questions concerning this license, you may contact Activision at 3100 Ocean Park Boulevard, Santa Monica, California 90405, USA, (310) 255-2000, Attn. Business and Legal Affairs, legal@activision.com. +\par }\pard\plain \ql \li0\ri0\widctlpar\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \f27\fs24\cf1\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\f1\fs20 +\par }} \ No newline at end of file diff --git a/base/maps/examples.map b/base/maps/examples.map new file mode 100644 index 0000000..128ef73 --- /dev/null +++ b/base/maps/examples.map @@ -0,0 +1,1496 @@ +{ +"music" "music/dm_col1" +"classname" "worldspawn" +// brush 0 +{ +( 2108 612 0 ) ( 2108 616 0 ) ( 2096 616 0 ) common/cncr_wall01b 320 704 0 0.125000 0.125000 0 0 0 +( 2096 616 4 ) ( 2108 616 4 ) ( 2108 612 4 ) common/cncr_wall01b 320 704 0 0.125000 0.125000 0 0 0 +( 2120 612 0 ) ( 2112 620 0 ) ( 2112 620 4 ) common/cncr_wall01b 32 -480 0 0.125000 0.125000 0 0 0 +( 2112 636 4 ) ( 2112 620 4 ) ( 2112 620 0 ) common/cncr_wall01b -864 -480 0 0.125000 0.125000 0 0 0 +( 2120 640 4 ) ( 2112 632 4 ) ( 2112 632 0 ) common/cncr_wall01b 32 -480 0 0.125000 0.125000 0 0 0 +( 2120 608 0 ) ( 2120 608 4 ) ( 2120 612 4 ) common/cncr_wall01b -864 -480 0 0.125000 0.125000 0 0 0 +} +// brush 1 +{ +( 2152 616 0 ) ( 2140 616 0 ) ( 2140 612 0 ) common/cncr_wall01b 192 704 0 0.125000 0.125000 0 0 0 +( 2140 612 4 ) ( 2140 616 4 ) ( 2152 616 4 ) common/cncr_wall01b 192 704 0 0.125000 0.125000 0 0 0 +( 2136 620 4 ) ( 2136 620 0 ) ( 2128 612 0 ) common/cncr_wall01b 32 -480 0 0.125000 0.125000 0 0 0 +( 2136 620 0 ) ( 2136 620 4 ) ( 2136 636 4 ) common/cncr_wall01b -864 -480 0 0.125000 0.125000 0 0 0 +( 2136 632 0 ) ( 2136 632 4 ) ( 2128 640 4 ) common/cncr_wall01b 32 -480 0 0.125000 0.125000 0 0 0 +( 2128 612 4 ) ( 2128 608 4 ) ( 2128 608 0 ) common/cncr_wall01b -864 -480 0 0.125000 0.125000 0 0 0 +} +// brush 2 +{ +( 2152 596 0 ) ( 2140 596 0 ) ( 2140 592 0 ) common/cncr_wall01b 192 544 0 0.125000 0.125000 0 0 0 +( 2140 592 4 ) ( 2140 596 4 ) ( 2152 596 4 ) common/cncr_wall01b 192 544 0 0.125000 0.125000 0 0 0 +( 2136 600 4 ) ( 2136 600 0 ) ( 2128 592 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 2136 600 0 ) ( 2136 600 4 ) ( 2136 616 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 2136 612 0 ) ( 2136 612 4 ) ( 2128 620 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 2128 592 4 ) ( 2128 588 4 ) ( 2128 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 3 +{ +( 2136 596 0 ) ( 2124 596 0 ) ( 2124 592 0 ) common/cncr_wall01b 320 544 0 0.125000 0.125000 0 0 0 +( 2124 592 4 ) ( 2124 596 4 ) ( 2136 596 4 ) common/cncr_wall01b 320 544 0 0.125000 0.125000 0 0 0 +( 2124 592 4 ) ( 2136 592 4 ) ( 2136 592 0 ) common/cncr_wall01b 320 -480 0 0.125000 0.125000 0 0 0 +( 2128 592 4 ) ( 2128 596 4 ) ( 2128 596 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 2136 600 4 ) ( 2124 600 4 ) ( 2124 600 0 ) common/cncr_wall01b 320 -480 0 0.125000 0.125000 0 0 0 +( 2120 592 4 ) ( 2120 588 4 ) ( 2120 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 4 +{ +( 2124 608 0 ) ( 2124 620 0 ) ( 2120 620 0 ) common/cncr_wall01b 320 512 0 0.125000 0.125000 0 0 0 +( 2124 620 4 ) ( 2124 608 4 ) ( 2120 608 4 ) common/cncr_wall01b 320 512 0 0.125000 0.125000 0 0 0 +( 2120 612 0 ) ( 2120 612 4 ) ( 2124 612 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( 2128 608 0 ) ( 2128 608 4 ) ( 2128 620 4 ) common/cncr_wall01b -512 0 0 0.125000 0.125000 0 0 0 +( 2128 620 0 ) ( 2128 620 4 ) ( 2124 620 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( 2120 608 4 ) ( 2120 608 0 ) ( 2120 620 0 ) common/cncr_wall01b -512 0 0 0.125000 0.125000 0 0 0 +} +// brush 5 +{ +( 2136 640 0 ) ( 2120 640 0 ) ( 2120 592 0 ) common/cncr_wall01b 320 640 0 0.125000 0.125000 0 0 0 +( 2120 592 4 ) ( 2120 640 4 ) ( 2136 640 4 ) common/cncr_wall01b 320 640 0 0.125000 0.125000 0 0 0 +( 2124 632 88 ) ( 2140 632 88 ) ( 2140 632 0 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +( 2128 592 88 ) ( 2128 640 88 ) ( 2128 640 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( 2128 640 88 ) ( 2112 640 88 ) ( 2112 640 0 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +( 2120 640 88 ) ( 2120 592 88 ) ( 2120 592 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +} +// brush 6 +{ +( 2108 592 0 ) ( 2108 596 0 ) ( 2096 596 0 ) common/cncr_wall01b 320 544 0 0.125000 0.125000 0 0 0 +( 2096 596 4 ) ( 2108 596 4 ) ( 2108 592 4 ) common/cncr_wall01b 320 544 0 0.125000 0.125000 0 0 0 +( 2120 592 0 ) ( 2112 600 0 ) ( 2112 600 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 2112 616 4 ) ( 2112 600 4 ) ( 2112 600 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 2120 620 4 ) ( 2112 612 4 ) ( 2112 612 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 2120 588 0 ) ( 2120 588 4 ) ( 2120 592 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 7 +{ +( 2432 88 48 ) ( 2432 344 48 ) ( 2432 344 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 2368 352 48 ) ( 2048 352 48 ) ( 2048 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 352 48 ) ( 2048 96 48 ) ( 2048 96 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 2304 336 48 ) ( 2624 336 48 ) ( 2304 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 96 224 ) ( 2048 352 224 ) ( 2368 352 224 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 2368 352 0 ) ( 2048 352 0 ) ( 2048 96 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 8 +{ +( 2432 456 48 ) ( 2432 712 48 ) ( 2432 712 -16 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +( 2368 720 48 ) ( 2048 720 48 ) ( 2048 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 720 48 ) ( 2048 464 48 ) ( 2048 464 -16 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +( 2304 704 48 ) ( 2624 704 48 ) ( 2304 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 464 224 ) ( 2048 720 224 ) ( 2368 720 224 ) common/asphault -512 -512 0 0.125000 0.125000 0 0 0 +( 2368 720 0 ) ( 2048 720 0 ) ( 2048 464 0 ) common/asphault -512 -512 0 0.125000 0.125000 0 0 0 +} +// brush 9 +{ +( 2048 -112 240 ) ( 2048 144 240 ) ( 2368 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 2008 336 48 ) ( 2328 336 48 ) ( 2328 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2432 -112 48 ) ( 2432 144 48 ) ( 2432 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 2432 720 48 ) ( 2112 720 48 ) ( 2112 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 144 48 ) ( 2048 -112 48 ) ( 2048 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 2048 144 224 ) ( 2048 -112 224 ) ( 2368 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 10 +{ +( 2048 144 48 ) ( 2048 -112 48 ) ( 2048 -112 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( 2048 144 0 ) ( 2368 144 0 ) ( 2048 -112 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 2368 720 48 ) ( 2048 720 48 ) ( 2048 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 2432 -112 48 ) ( 2432 144 48 ) ( 2432 144 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( 2008 336 48 ) ( 2328 336 48 ) ( 2328 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 2368 144 -16 ) ( 2048 144 -16 ) ( 2048 -112 -16 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 11 +{ +( 2056 592 0 ) ( 1736 592 0 ) ( 1736 336 0 ) common/asphault -64 -512 0 0.125000 0.125000 0 0 0 +( 2056 576 0 ) ( 2056 576 224 ) ( 2040 576 224 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( 1736 352 48 ) ( 2056 352 48 ) ( 2056 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( 1736 336 224 ) ( 1736 592 224 ) ( 2056 592 224 ) common/asphault -64 -512 0 0.125000 0.125000 0 0 0 +( 2040 592 48 ) ( 2040 336 48 ) ( 2040 592 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( 2056 576 224 ) ( 2056 576 0 ) ( 2056 352 0 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +} +// brush 12 +{ +( 1728 592 0 ) ( 1736 592 0 ) ( 1752 640 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 1736 592 4 ) ( 1728 592 4 ) ( 1744 640 4 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 1736 592 4 ) ( 1736 592 0 ) ( 1728 592 0 ) common/cncr_wall01b 192 -480 0 0.125000 0.125000 0 0 0 +( 1736 592 0 ) ( 1736 592 4 ) ( 1752 640 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 1744 640 0 ) ( 1752 640 0 ) ( 1752 640 4 ) common/cncr_wall01b 192 -480 0 0.125000 0.125000 0 0 0 +( 1728 592 4 ) ( 1728 592 0 ) ( 1744 640 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 13 +{ +( 1752 640 0 ) ( 1736 640 0 ) ( 1736 592 0 ) common/cncr_wall01b -704 640 0 0.125000 0.125000 0 0 0 +( 1736 592 4 ) ( 1736 640 4 ) ( 1752 640 4 ) common/cncr_wall01b -704 640 0 0.125000 0.125000 0 0 0 +( 1740 632 88 ) ( 1756 632 88 ) ( 1756 632 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( 1748 592 88 ) ( 1748 640 88 ) ( 1748 640 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( 1736 640 88 ) ( 1720 640 88 ) ( 1720 640 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( 1728 640 88 ) ( 1728 592 88 ) ( 1728 592 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +} +// brush 14 +{ +( 1672 592 0 ) ( 1352 592 0 ) ( 1352 336 0 ) common/asphault -64 -512 0 0.125000 0.125000 0 0 0 +( 1672 576 0 ) ( 1672 576 224 ) ( 1656 576 224 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( 1352 352 48 ) ( 1672 352 48 ) ( 1672 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( 1352 336 224 ) ( 1352 592 224 ) ( 1672 592 224 ) common/asphault -64 -512 0 0.125000 0.125000 0 0 0 +( 1656 592 48 ) ( 1656 336 48 ) ( 1656 592 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( 1672 576 224 ) ( 1672 576 0 ) ( 1672 352 0 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +} +// brush 15 +{ +( 1664 144 48 ) ( 1664 -112 48 ) ( 1664 -112 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( 1664 144 0 ) ( 1984 144 0 ) ( 1664 -112 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 1984 720 48 ) ( 1664 720 48 ) ( 1664 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 2048 -112 48 ) ( 2048 144 48 ) ( 2048 144 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( 1624 336 48 ) ( 1944 336 48 ) ( 1944 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1984 144 -16 ) ( 1664 144 -16 ) ( 1664 -112 -16 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 16 +{ +( 1664 -112 240 ) ( 1664 144 240 ) ( 1984 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 1624 336 48 ) ( 1944 336 48 ) ( 1944 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 2048 -112 48 ) ( 2048 144 48 ) ( 2048 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 2048 720 48 ) ( 1728 720 48 ) ( 1728 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 144 48 ) ( 1664 -112 48 ) ( 1664 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 1664 144 224 ) ( 1664 -112 224 ) ( 1984 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 17 +{ +( 2048 456 48 ) ( 2048 712 48 ) ( 2048 712 -16 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +( 1984 720 48 ) ( 1664 720 48 ) ( 1664 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 720 48 ) ( 1664 464 48 ) ( 1664 464 -16 ) common/asphault 384 -128 0 0.125000 0.125000 0 0 0 +( 1920 704 48 ) ( 2240 704 48 ) ( 1920 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 464 224 ) ( 1664 720 224 ) ( 1984 720 224 ) common/asphault -512 -512 0 0.125000 0.125000 0 0 0 +( 1984 720 0 ) ( 1664 720 0 ) ( 1664 464 0 ) common/asphault -512 -512 0 0.125000 0.125000 0 0 0 +} +// brush 18 +{ +( 2048 88 48 ) ( 2048 344 48 ) ( 2048 344 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 1984 352 48 ) ( 1664 352 48 ) ( 1664 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 352 48 ) ( 1664 96 48 ) ( 1664 96 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 1920 336 48 ) ( 2240 336 48 ) ( 1920 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 96 224 ) ( 1664 352 224 ) ( 1984 352 224 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 1984 352 0 ) ( 1664 352 0 ) ( 1664 96 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 19 +{ +( 1478 586 0 ) ( 1478 592 0 ) ( 1468 592 0 ) common/cncr_wall01b -896 560 0 0.125000 0.125000 0 0 0 +( 1468 592 4 ) ( 1478 592 4 ) ( 1478 586 4 ) common/cncr_wall01b -896 560 0 0.125000 0.125000 0 0 0 +( 1468 586 0 ) ( 1468 586 4 ) ( 1478 586 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1466 592 0 ) ( 1466 592 4 ) ( 1466 586 4 ) common/cncr_wall01b -848 544 0 0.125000 0.125000 0 0 0 +( 1468 606 0 ) ( 1468 606 4 ) ( 1458 606 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1472 586 0 ) ( 1472 586 4 ) ( 1472 592 4 ) common/cncr_wall01b -848 544 0 0.125000 0.125000 0 0 0 +} +// brush 20 +{ +( 1478 606 0 ) ( 1478 612 0 ) ( 1468 612 0 ) common/cncr_wall01b -896 720 0 0.125000 0.125000 0 0 0 +( 1468 612 4 ) ( 1478 612 4 ) ( 1478 606 4 ) common/cncr_wall01b -896 720 0 0.125000 0.125000 0 0 0 +( 1468 606 0 ) ( 1468 606 4 ) ( 1478 606 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1466 612 0 ) ( 1466 612 4 ) ( 1466 606 4 ) common/cncr_wall01b 16 544 0 0.125000 0.125000 0 0 0 +( 1478 612 0 ) ( 1478 612 4 ) ( 1468 612 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1478 606 0 ) ( 1478 606 4 ) ( 1478 612 4 ) common/cncr_wall01b 16 544 0 0.125000 0.125000 0 0 0 +} +// brush 21 +{ +( 1478 580 0 ) ( 1478 586 0 ) ( 1468 586 0 ) common/cncr_wall01b -896 512 0 0.125000 0.125000 0 0 0 +( 1468 586 4 ) ( 1478 586 4 ) ( 1478 580 4 ) common/cncr_wall01b -896 512 0 0.125000 0.125000 0 0 0 +( 1468 580 0 ) ( 1468 580 4 ) ( 1478 580 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1466 586 0 ) ( 1466 586 4 ) ( 1466 580 4 ) common/cncr_wall01b -800 544 0 0.125000 0.125000 0 0 0 +( 1474 586 0 ) ( 1474 586 4 ) ( 1464 586 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1478 580 0 ) ( 1478 580 4 ) ( 1478 586 4 ) common/cncr_wall01b -800 544 0 0.125000 0.125000 0 0 0 +} +// brush 22 +{ +( 1486 580 0 ) ( 1486 612 0 ) ( 1478 612 0 ) common/cncr_wall01b -896 512 0 0.125000 0.125000 0 0 0 +( 1478 612 4 ) ( 1486 612 4 ) ( 1486 580 4 ) common/cncr_wall01b -896 512 0 0.125000 0.125000 0 0 0 +( 1478 580 4 ) ( 1486 586 4 ) ( 1486 586 0 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1478 612 0 ) ( 1478 612 4 ) ( 1478 580 4 ) common/cncr_wall01b -800 544 0 0.125000 0.125000 0 0 0 +( 1478 612 0 ) ( 1486 606 0 ) ( 1486 606 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 1486 606 0 ) ( 1486 586 0 ) ( 1486 586 4 ) common/cncr_wall01b -800 544 0 0.125000 0.125000 0 0 0 +} +// brush 23 +{ +( 1280 144 48 ) ( 1280 -112 48 ) ( 1280 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1280 144 0 ) ( 1600 144 0 ) ( 1280 -112 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1600 720 48 ) ( 1280 720 48 ) ( 1280 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1664 -112 48 ) ( 1664 144 48 ) ( 1664 144 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1240 336 48 ) ( 1560 336 48 ) ( 1560 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1600 144 -16 ) ( 1280 144 -16 ) ( 1280 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 24 +{ +( 1280 -112 240 ) ( 1280 144 240 ) ( 1600 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 1240 336 48 ) ( 1560 336 48 ) ( 1560 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1664 -112 48 ) ( 1664 144 48 ) ( 1664 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 1664 720 48 ) ( 1344 720 48 ) ( 1344 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 144 48 ) ( 1280 -112 48 ) ( 1280 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 1280 144 224 ) ( 1280 -112 224 ) ( 1600 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 25 +{ +( 1664 456 48 ) ( 1664 712 48 ) ( 1664 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 1600 720 48 ) ( 1280 720 48 ) ( 1280 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 720 48 ) ( 1280 464 48 ) ( 1280 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 1536 704 48 ) ( 1856 704 48 ) ( 1536 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 464 224 ) ( 1280 720 224 ) ( 1600 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 1600 720 0 ) ( 1280 720 0 ) ( 1280 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 26 +{ +( 1664 88 48 ) ( 1664 344 48 ) ( 1664 344 -16 ) common/asphault -768 -128 0 0.125000 0.125000 0 0 0 +( 1600 352 48 ) ( 1280 352 48 ) ( 1280 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 352 48 ) ( 1280 96 48 ) ( 1280 96 -16 ) common/asphault -768 -128 0 0.125000 0.125000 0 0 0 +( 1536 336 48 ) ( 1856 336 48 ) ( 1536 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 96 224 ) ( 1280 352 224 ) ( 1600 352 224 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1600 352 0 ) ( 1280 352 0 ) ( 1280 96 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 27 +{ +( 1476 502 0 ) ( 1470 502 0 ) ( 1470 476 0 ) colombia/canvas_projclip -448 0 0 0.125000 0.125000 134217728 0 0 +( 1470 476 172 ) ( 1470 502 172 ) ( 1476 502 172 ) colombia/canvas_projclip -448 0 0 0.125000 0.125000 134217728 0 0 +( 1470 448 52 ) ( 1476 448 52 ) ( 1476 448 4 ) colombia/canvas_projclip -448 0 0 0.125000 0.125000 134217728 0 0 +( 1474 668 52 ) ( 1474 694 52 ) ( 1474 694 4 ) colombia/canvas_projclip 0 0 0 0.125000 0.125000 134217728 0 0 +( 1476 576 52 ) ( 1470 576 52 ) ( 1470 576 4 ) colombia/canvas_projclip -448 0 0 0.125000 0.125000 134217728 0 0 +( 1470 438 180 ) ( 1470 412 180 ) ( 1470 412 132 ) colombia/canvas_projclip 0 0 0 0.125000 0.125000 134217728 0 0 +} +// brush 28 +{ +( 1086 616 0 ) ( 1078 616 0 ) ( 1078 584 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1078 584 4 ) ( 1078 616 4 ) ( 1086 616 4 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1078 590 0 ) ( 1078 590 4 ) ( 1086 584 4 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1086 584 4 ) ( 1086 616 4 ) ( 1086 616 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 1078 610 4 ) ( 1078 610 0 ) ( 1086 616 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1078 590 4 ) ( 1078 590 0 ) ( 1078 610 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +} +// brush 29 +{ +( 1096 590 0 ) ( 1086 590 0 ) ( 1086 584 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1086 584 4 ) ( 1086 590 4 ) ( 1096 590 4 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1086 584 4 ) ( 1096 584 4 ) ( 1096 584 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1098 584 4 ) ( 1098 590 4 ) ( 1098 590 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 1100 590 4 ) ( 1090 590 4 ) ( 1090 590 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1086 590 4 ) ( 1086 584 4 ) ( 1086 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +} +// brush 30 +{ +( 1096 616 0 ) ( 1086 616 0 ) ( 1086 610 0 ) common/cncr_wall01b -864 752 0 0.125000 0.125000 0 0 0 +( 1086 610 4 ) ( 1086 616 4 ) ( 1096 616 4 ) common/cncr_wall01b -864 752 0 0.125000 0.125000 0 0 0 +( 1086 610 4 ) ( 1096 610 4 ) ( 1096 610 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1098 610 4 ) ( 1098 616 4 ) ( 1098 616 0 ) common/cncr_wall01b -16 544 0 0.125000 0.125000 0 0 0 +( 1096 616 4 ) ( 1086 616 4 ) ( 1086 616 0 ) common/cncr_wall01b -864 544 0 0.125000 0.125000 0 0 0 +( 1086 616 4 ) ( 1086 610 4 ) ( 1086 610 0 ) common/cncr_wall01b -16 544 0 0.125000 0.125000 0 0 0 +} +// brush 31 +{ +( 708 603 0 ) ( 698 603 0 ) ( 698 597 0 ) common/cncr_wall01b -832 648 0 0.125000 0.125000 0 0 0 +( 698 597 4 ) ( 698 603 4 ) ( 708 603 4 ) common/cncr_wall01b -832 648 0 0.125000 0.125000 0 0 0 +( 698 597 4 ) ( 708 597 4 ) ( 708 597 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 706 597 4 ) ( 706 603 4 ) ( 706 603 0 ) common/cncr_wall01b 88 544 0 0.125000 0.125000 0 0 0 +( 708 603 4 ) ( 698 603 4 ) ( 698 603 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 603 4 ) ( 698 597 4 ) ( 698 597 0 ) common/cncr_wall01b 88 544 0 0.125000 0.125000 0 0 0 +} +// brush 32 +{ +( 708 616 0 ) ( 698 616 0 ) ( 698 610 0 ) common/cncr_wall01b -832 752 0 0.125000 0.125000 0 0 0 +( 698 610 4 ) ( 698 616 4 ) ( 708 616 4 ) common/cncr_wall01b -832 752 0 0.125000 0.125000 0 0 0 +( 698 610 4 ) ( 708 610 4 ) ( 708 610 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 706 610 4 ) ( 706 616 4 ) ( 706 616 0 ) common/cncr_wall01b -16 544 0 0.125000 0.125000 0 0 0 +( 708 616 4 ) ( 698 616 4 ) ( 698 616 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 616 4 ) ( 698 610 4 ) ( 698 610 0 ) common/cncr_wall01b -16 544 0 0.125000 0.125000 0 0 0 +} +// brush 33 +{ +( 716 606 0 ) ( 706 606 0 ) ( 706 600 0 ) common/cncr_wall01b -896 672 0 0.125000 0.125000 0 0 0 +( 706 600 4 ) ( 706 606 4 ) ( 716 606 4 ) common/cncr_wall01b -896 672 0 0.125000 0.125000 0 0 0 +( 714 606 4 ) ( 714 606 0 ) ( 706 600 0 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 714 610 4 ) ( 714 610 0 ) ( 714 606 0 ) common/cncr_wall01b -960 544 0 0.125000 0.125000 0 0 0 +( 714 610 0 ) ( 714 610 4 ) ( 706 616 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 706 606 4 ) ( 706 600 4 ) ( 706 600 0 ) common/cncr_wall01b -960 544 0 0.125000 0.125000 0 0 0 +} +// brush 34 +{ +( 716 590 0 ) ( 706 590 0 ) ( 706 584 0 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 706 584 4 ) ( 706 590 4 ) ( 716 590 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 714 590 4 ) ( 714 590 0 ) ( 706 584 0 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 714 594 4 ) ( 714 594 0 ) ( 714 590 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 714 594 0 ) ( 714 594 4 ) ( 706 600 4 ) common/cncr_wall01b -896 544 0 0.125000 0.125000 0 0 0 +( 706 590 4 ) ( 706 584 4 ) ( 706 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +} +// brush 35 +{ +( 708 590 0 ) ( 698 590 0 ) ( 698 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 584 4 ) ( 698 590 4 ) ( 708 590 4 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 584 4 ) ( 708 584 4 ) ( 708 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 706 584 4 ) ( 706 590 4 ) ( 706 590 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 708 590 4 ) ( 698 590 4 ) ( 698 590 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 590 4 ) ( 698 584 4 ) ( 698 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +} +// brush 36 +{ +( 698 616 0 ) ( 690 616 0 ) ( 690 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 690 584 4 ) ( 690 616 4 ) ( 698 616 4 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 690 584 4 ) ( 698 584 4 ) ( 698 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 584 4 ) ( 698 616 4 ) ( 698 616 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 698 616 4 ) ( 690 616 4 ) ( 690 616 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 690 616 4 ) ( 690 584 4 ) ( 690 584 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +} +// brush 37 +{ +( 332 592 0 ) ( 320 592 0 ) ( 320 588 0 ) common/cncr_wall01b -608 512 0 0.125000 0.125000 0 0 0 +( 320 588 4 ) ( 320 592 4 ) ( 332 592 4 ) common/cncr_wall01b -608 512 0 0.125000 0.125000 0 0 0 +( 320 588 4 ) ( 332 588 4 ) ( 332 588 0 ) common/cncr_wall01b -608 -480 0 0.125000 0.125000 0 0 0 +( 332 596 4 ) ( 320 596 4 ) ( 320 596 0 ) common/cncr_wall01b -608 -480 0 0.125000 0.125000 0 0 0 +( 314 584 0 ) ( 320 604 0 ) ( 317 594 4 ) common/cncr_wall01b -672 -480 0 0.125000 0.125000 0 0 0 +( 320 602 0 ) ( 324 584 0 ) ( 322 593 4 ) common/cncr_wall01b -672 -480 0 0.125000 0.125000 0 0 0 +} +// brush 38 +{ +( 306 584 0 ) ( 314 584 0 ) ( 324 616 0 ) common/cncr_wall01b -608 480 0 0.125000 0.125000 0 0 0 +( 314 584 4 ) ( 306 584 4 ) ( 316 616 4 ) common/cncr_wall01b -608 480 0 0.125000 0.125000 0 0 0 +( 306 584 0 ) ( 306 584 4 ) ( 310 584 4 ) common/cncr_wall01b -544 0 0 0.125000 0.125000 0 0 0 +( 314 584 0 ) ( 314 584 4 ) ( 324 616 4 ) common/cncr_wall01b -480 0 0 0.125000 0.125000 0 0 0 +( 324 616 0 ) ( 324 616 4 ) ( 320 616 4 ) common/cncr_wall01b -544 0 0 0.125000 0.125000 0 0 0 +( 306 584 4 ) ( 306 584 0 ) ( 316 616 0 ) common/cncr_wall01b -480 0 0 0.125000 0.125000 0 0 0 +( 320 616 0 ) ( 320 600 0 ) ( 320 608 4 ) common/cncr_wall01b -480 0 0 0.125000 0.125000 0 0 0 +} +// brush 39 +{ +( 324 616 0 ) ( 316 616 0 ) ( 324 584 0 ) common/cncr_wall01b -736 480 0 0.125000 0.125000 0 0 0 +( 316 616 4 ) ( 324 616 4 ) ( 332 584 4 ) common/cncr_wall01b -736 480 0 0.125000 0.125000 0 0 0 +( 332 584 4 ) ( 332 584 0 ) ( 324 584 0 ) common/cncr_wall01b -736 -480 0 0.125000 0.125000 0 0 0 +( 324 616 4 ) ( 324 616 0 ) ( 332 584 0 ) common/cncr_wall01b -640 -480 0 0.125000 0.125000 0 0 0 +( 324 616 0 ) ( 324 616 4 ) ( 316 616 4 ) common/cncr_wall01b -736 -480 0 0.125000 0.125000 0 0 0 +( 320 602 0 ) ( 320 602 4 ) ( 324 584 4 ) common/cncr_wall01b -640 -480 0 0.125000 0.125000 0 0 0 +( 320 602 4 ) ( 320 602 0 ) ( 320 616 0 ) common/cncr_wall01b -640 -480 0 0.125000 0.125000 0 0 0 +} +// brush 40 +{ +( 188 592 0 ) ( 188 596 0 ) ( 176 596 0 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 176 596 4 ) ( 188 596 4 ) ( 188 592 4 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 200 592 0 ) ( 192 600 0 ) ( 192 600 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 192 616 4 ) ( 192 600 4 ) ( 192 600 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 200 640 4 ) ( 192 632 4 ) ( 192 632 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 200 588 0 ) ( 200 588 4 ) ( 200 592 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 41 +{ +( 216 640 0 ) ( 200 640 0 ) ( 200 592 0 ) common/cncr_wall01b -704 640 0 0.125000 0.125000 0 0 0 +( 200 592 4 ) ( 200 640 4 ) ( 216 640 4 ) common/cncr_wall01b -704 640 0 0.125000 0.125000 0 0 0 +( 204 632 88 ) ( 220 632 88 ) ( 220 632 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( 212 592 88 ) ( 212 640 88 ) ( 212 640 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( 208 640 88 ) ( 192 640 88 ) ( 192 640 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( 200 640 88 ) ( 200 592 88 ) ( 200 592 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +} +// brush 42 +{ +( 204 612 0 ) ( 204 624 0 ) ( 200 624 0 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 204 624 4 ) ( 204 612 4 ) ( 200 612 4 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 200 616 0 ) ( 200 616 4 ) ( 204 616 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( 208 612 0 ) ( 208 612 4 ) ( 208 624 4 ) common/cncr_wall01b -544 0 0 0.125000 0.125000 0 0 0 +( 208 624 0 ) ( 208 624 4 ) ( 204 624 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( 200 612 4 ) ( 200 612 0 ) ( 200 624 0 ) common/cncr_wall01b -544 0 0 0.125000 0.125000 0 0 0 +} +// brush 43 +{ +( 216 596 0 ) ( 204 596 0 ) ( 204 592 0 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 204 592 4 ) ( 204 596 4 ) ( 216 596 4 ) common/cncr_wall01b -704 544 0 0.125000 0.125000 0 0 0 +( 204 592 4 ) ( 216 592 4 ) ( 216 592 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 208 592 4 ) ( 208 596 4 ) ( 208 596 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 216 600 4 ) ( 204 600 4 ) ( 204 600 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 200 592 4 ) ( 200 588 4 ) ( 200 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 44 +{ +( 232 596 0 ) ( 220 596 0 ) ( 220 592 0 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 220 592 4 ) ( 220 596 4 ) ( 232 596 4 ) common/cncr_wall01b -832 544 0 0.125000 0.125000 0 0 0 +( 216 600 4 ) ( 216 600 0 ) ( 208 592 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 216 600 0 ) ( 216 600 4 ) ( 216 616 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( 216 616 0 ) ( 216 616 4 ) ( 208 624 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( 208 592 4 ) ( 208 588 4 ) ( 208 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 45 +{ +( 1280 88 48 ) ( 1280 344 48 ) ( 1280 344 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 1216 352 48 ) ( 896 352 48 ) ( 896 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 352 48 ) ( 896 96 48 ) ( 896 96 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 1152 336 48 ) ( 1472 336 48 ) ( 1152 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 96 224 ) ( 896 352 224 ) ( 1216 352 224 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 1216 352 0 ) ( 896 352 0 ) ( 896 96 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 46 +{ +( 1280 456 48 ) ( 1280 712 48 ) ( 1280 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 1216 720 48 ) ( 896 720 48 ) ( 896 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 720 48 ) ( 896 464 48 ) ( 896 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 1152 704 48 ) ( 1472 704 48 ) ( 1152 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 464 224 ) ( 896 720 224 ) ( 1216 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 1216 720 0 ) ( 896 720 0 ) ( 896 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 47 +{ +( 896 -112 240 ) ( 896 144 240 ) ( 1216 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 856 336 48 ) ( 1176 336 48 ) ( 1176 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 1280 -112 48 ) ( 1280 144 48 ) ( 1280 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 1280 720 48 ) ( 960 720 48 ) ( 960 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 144 48 ) ( 896 -112 48 ) ( 896 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 896 144 224 ) ( 896 -112 224 ) ( 1216 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 48 +{ +( 896 144 48 ) ( 896 -112 48 ) ( 896 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 896 144 0 ) ( 1216 144 0 ) ( 896 -112 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1216 720 48 ) ( 896 720 48 ) ( 896 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1280 -112 48 ) ( 1280 144 48 ) ( 1280 144 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 856 336 48 ) ( 1176 336 48 ) ( 1176 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 1216 144 -16 ) ( 896 144 -16 ) ( 896 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 49 +{ +( 512 144 48 ) ( 512 -112 48 ) ( 512 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 512 144 0 ) ( 832 144 0 ) ( 512 -112 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 832 720 48 ) ( 512 720 48 ) ( 512 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 896 -112 48 ) ( 896 144 48 ) ( 896 144 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 472 336 48 ) ( 792 336 48 ) ( 792 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 832 144 -16 ) ( 512 144 -16 ) ( 512 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 50 +{ +( 512 -112 240 ) ( 512 144 240 ) ( 832 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 472 336 48 ) ( 792 336 48 ) ( 792 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 896 -112 48 ) ( 896 144 48 ) ( 896 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 896 720 48 ) ( 576 720 48 ) ( 576 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 144 48 ) ( 512 -112 48 ) ( 512 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 512 144 224 ) ( 512 -112 224 ) ( 832 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 51 +{ +( 896 456 48 ) ( 896 712 48 ) ( 896 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 832 720 48 ) ( 512 720 48 ) ( 512 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 720 48 ) ( 512 464 48 ) ( 512 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 768 704 48 ) ( 1088 704 48 ) ( 768 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 464 224 ) ( 512 720 224 ) ( 832 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 832 720 0 ) ( 512 720 0 ) ( 512 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 52 +{ +( 896 88 48 ) ( 896 344 48 ) ( 896 344 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 832 352 48 ) ( 512 352 48 ) ( 512 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 352 48 ) ( 512 96 48 ) ( 512 96 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 768 336 48 ) ( 1088 336 48 ) ( 768 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 96 224 ) ( 512 352 224 ) ( 832 352 224 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 832 352 0 ) ( 512 352 0 ) ( 512 96 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 53 +{ +( 512 88 48 ) ( 512 344 48 ) ( 512 344 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 448 352 48 ) ( 128 352 48 ) ( 128 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 352 48 ) ( 128 96 48 ) ( 128 96 -16 ) common/asphault 256 -128 0 0.125000 0.125000 0 0 0 +( 384 336 48 ) ( 704 336 48 ) ( 384 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 96 224 ) ( 128 352 224 ) ( 448 352 224 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +( 448 352 0 ) ( 128 352 0 ) ( 128 96 0 ) common/asphault -512 -384 0 0.125000 0.125000 0 0 0 +} +// brush 54 +{ +( 512 456 48 ) ( 512 712 48 ) ( 512 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 448 720 48 ) ( 128 720 48 ) ( 128 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 720 48 ) ( 128 464 48 ) ( 128 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 384 704 48 ) ( 704 704 48 ) ( 384 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 464 224 ) ( 128 720 224 ) ( 448 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 448 720 0 ) ( 128 720 0 ) ( 128 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 55 +{ +( 128 -112 240 ) ( 128 144 240 ) ( 448 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( 88 336 48 ) ( 408 336 48 ) ( 408 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 512 -112 48 ) ( 512 144 48 ) ( 512 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 512 720 48 ) ( 192 720 48 ) ( 192 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 144 48 ) ( 128 -112 48 ) ( 128 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 128 144 224 ) ( 128 -112 224 ) ( 448 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 56 +{ +( 128 144 48 ) ( 128 -112 48 ) ( 128 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 128 144 0 ) ( 448 144 0 ) ( 128 -112 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 448 720 48 ) ( 128 720 48 ) ( 128 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 512 -112 48 ) ( 512 144 48 ) ( 512 144 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 88 336 48 ) ( 408 336 48 ) ( 408 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 448 144 -16 ) ( 128 144 -16 ) ( 128 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 57 +{ +( -152 596 0 ) ( -164 596 0 ) ( -164 592 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( -164 592 4 ) ( -164 596 4 ) ( -152 596 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( -168 600 4 ) ( -168 600 0 ) ( -176 592 0 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( -168 600 0 ) ( -168 600 4 ) ( -168 616 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -168 616 0 ) ( -168 616 4 ) ( -176 624 4 ) common/cncr_wall01b -832 -480 0 0.125000 0.125000 0 0 0 +( -176 592 4 ) ( -176 588 4 ) ( -176 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 58 +{ +( -168 596 0 ) ( -180 596 0 ) ( -180 592 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -180 592 4 ) ( -180 596 4 ) ( -168 596 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -180 592 4 ) ( -168 592 4 ) ( -168 592 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -176 592 4 ) ( -176 596 4 ) ( -176 596 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -168 600 4 ) ( -180 600 4 ) ( -180 600 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -192 592 4 ) ( -192 588 4 ) ( -192 588 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +} +// brush 59 +{ +( -180 612 0 ) ( -180 624 0 ) ( -184 624 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -180 624 4 ) ( -180 612 4 ) ( -184 612 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -184 616 0 ) ( -184 616 4 ) ( -180 616 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -176 612 0 ) ( -176 612 4 ) ( -176 624 4 ) common/cncr_wall01b 480 0 0 0.125000 0.125000 0 0 0 +( -176 624 0 ) ( -176 624 4 ) ( -180 624 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -192 612 4 ) ( -192 612 0 ) ( -192 624 0 ) common/cncr_wall01b 480 0 0 0.125000 0.125000 0 0 0 +} +// brush 60 +{ +( -168 640 0 ) ( -184 640 0 ) ( -184 592 0 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -184 592 4 ) ( -184 640 4 ) ( -168 640 4 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -180 632 88 ) ( -164 632 88 ) ( -164 632 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -168 592 88 ) ( -168 640 88 ) ( -168 640 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -176 640 88 ) ( -192 640 88 ) ( -192 640 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -184 640 88 ) ( -184 592 88 ) ( -184 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 61 +{ +( -184 640 0 ) ( -192 640 0 ) ( -192 624 0 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -192 640 4 ) ( -184 640 4 ) ( -184 624 4 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -180 624 0 ) ( -180 624 4 ) ( -176 624 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -184 640 4 ) ( -184 640 0 ) ( -184 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -176 640 0 ) ( -176 640 4 ) ( -180 640 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -192 640 0 ) ( -192 640 4 ) ( -192 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 62 +{ +( 136 592 0 ) ( -184 592 0 ) ( -184 336 0 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( 136 576 0 ) ( 136 576 224 ) ( 120 576 224 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -184 352 48 ) ( 136 352 48 ) ( 136 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( -184 336 224 ) ( -184 592 224 ) ( 136 592 224 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( 120 592 48 ) ( 120 336 48 ) ( 120 592 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 136 576 224 ) ( 136 576 0 ) ( 136 352 0 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +} +// brush 63 +{ +( -320 336 48 ) ( 0 336 48 ) ( 0 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 320 48 ) ( 128 576 48 ) ( 128 576 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -256 592 48 ) ( -256 336 48 ) ( -256 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 320 352 48 ) ( 0 352 48 ) ( 320 352 -16 ) common/cncr_wall01b 0 0 0 0.125000 0.125000 0 0 0 +( -256 336 224 ) ( -256 592 224 ) ( 64 592 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 256 336 0 ) ( 320 336 0 ) ( 288 352 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 64 +{ +( 128 456 48 ) ( 128 712 48 ) ( 128 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 64 720 48 ) ( -256 720 48 ) ( -256 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -256 720 48 ) ( -256 464 48 ) ( -256 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( 0 704 48 ) ( 320 704 48 ) ( 0 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -256 464 224 ) ( -256 720 224 ) ( 64 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( 64 720 0 ) ( -256 720 0 ) ( -256 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 65 +{ +( -256 144 48 ) ( -256 -112 48 ) ( -256 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( -256 144 0 ) ( 64 144 0 ) ( -256 -112 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 64 720 48 ) ( -256 720 48 ) ( -256 720 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 128 -112 48 ) ( 128 144 48 ) ( 128 144 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( -296 336 48 ) ( 24 336 48 ) ( 24 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( 64 144 -16 ) ( -256 144 -16 ) ( -256 -112 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 66 +{ +( -256 -112 240 ) ( -256 144 240 ) ( 64 144 240 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +( -296 336 48 ) ( 24 336 48 ) ( 24 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( 128 -112 48 ) ( 128 144 48 ) ( 128 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( 128 720 48 ) ( -192 720 48 ) ( -192 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -256 144 48 ) ( -256 -112 48 ) ( -256 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( -256 144 224 ) ( -256 -112 224 ) ( 64 144 224 ) common/asphault -512 128 0 0.125000 0.125000 0 0 0 +} +// brush 67 +{ +( -564 640 0 ) ( -572 640 0 ) ( -576 624 0 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -572 640 4 ) ( -564 640 4 ) ( -568 624 4 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -564 624 0 ) ( -564 624 4 ) ( -560 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -564 640 4 ) ( -564 640 0 ) ( -568 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -556 640 0 ) ( -556 640 4 ) ( -560 640 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -572 640 0 ) ( -572 640 4 ) ( -576 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 68 +{ +( -552 640 0 ) ( -568 640 0 ) ( -568 592 0 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -568 592 4 ) ( -568 640 4 ) ( -552 640 4 ) common/cncr_wall01b -704 -384 0 0.125000 0.125000 0 0 0 +( -568 592 88 ) ( -552 592 88 ) ( -552 592 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -552 592 88 ) ( -552 640 88 ) ( -552 640 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -552 640 88 ) ( -568 640 88 ) ( -568 640 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -560 640 88 ) ( -560 592 88 ) ( -560 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 69 +{ +( -564 612 0 ) ( -564 624 0 ) ( -568 624 0 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -564 624 4 ) ( -564 612 4 ) ( -568 612 4 ) common/cncr_wall01b -704 -480 0 0.125000 0.125000 0 0 0 +( -568 616 0 ) ( -568 616 4 ) ( -564 616 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -560 612 0 ) ( -560 612 4 ) ( -560 624 4 ) common/cncr_wall01b 480 0 0 0.125000 0.125000 0 0 0 +( -560 624 0 ) ( -560 624 4 ) ( -564 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -576 612 4 ) ( -576 612 0 ) ( -576 624 0 ) common/cncr_wall01b 480 0 0 0.125000 0.125000 0 0 0 +} +// brush 70 +{ +( -944 632 4 ) ( -928 632 4 ) ( -928 584 4 ) common/cncr_wall01b -384 -447 -180 0.125000 -0.125000 0 0 0 +( -928 584 0 ) ( -928 632 0 ) ( -944 632 0 ) common/cncr_wall01b -384 -447 -180 0.125000 -0.125000 0 0 0 +( -948 608 0 ) ( -948 608 4 ) ( -936 608 4 ) common/cncr_wall01b -384 32 -180 0.125000 0.125000 0 0 0 +( -948 608 4 ) ( -948 608 0 ) ( -952 616 0 ) common/cncr_wall01b 448 32 0 0.125000 -0.125000 0 0 0 +( -940 616 0 ) ( -940 616 4 ) ( -952 616 4 ) common/cncr_wall01b -384 32 -180 0.125000 0.125000 0 0 0 +( -936 608 0 ) ( -936 608 4 ) ( -940 616 4 ) common/cncr_wall01b 448 32 0 0.125000 -0.125000 0 0 0 +} +// brush 71 +{ +( -956 600 0 ) ( -956 584 0 ) ( -948 584 0 ) common/cncr_wall01b -704 -576 0 0.125000 0.125000 0 0 0 +( -948 584 4 ) ( -956 584 4 ) ( -956 600 4 ) common/cncr_wall01b -704 -576 0 0.125000 0.125000 0 0 0 +( -948 592 4 ) ( -952 600 4 ) ( -952 600 0 ) common/cncr_wall01b 704 0 0 0.125000 0.125000 0 0 0 +( -948 592 0 ) ( -956 596 0 ) ( -956 596 4 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -956 604 4 ) ( -956 596 4 ) ( -956 596 0 ) common/cncr_wall01b 576 0 0 0.125000 0.125000 0 0 0 +( -956 604 0 ) ( -952 600 0 ) ( -952 600 4 ) common/cncr_wall01b 576 0 0 0.125000 0.125000 0 0 0 +} +// brush 72 +{ +( -952 596 0 ) ( -952 580 0 ) ( -944 580 0 ) common/cncr_wall01b -736 -544 0 0.125000 0.125000 0 0 0 +( -944 580 4 ) ( -952 580 4 ) ( -952 596 4 ) common/cncr_wall01b -736 -544 0 0.125000 0.125000 0 0 0 +( -940 596 4 ) ( -948 604 4 ) ( -948 604 0 ) common/cncr_wall01b 672 0 0 0.125000 0.125000 0 0 0 +( -940 596 0 ) ( -948 592 0 ) ( -948 592 4 ) common/cncr_wall01b -736 0 0 0.125000 0.125000 0 0 0 +( -948 592 0 ) ( -952 600 0 ) ( -952 600 4 ) common/cncr_wall01b 544 0 0 0.125000 0.125000 0 0 0 +( -948 604 4 ) ( -952 600 4 ) ( -952 600 0 ) common/cncr_wall01b 544 0 0 0.125000 0.125000 0 0 0 +} +// brush 73 +{ +( -948 608 0 ) ( -948 592 0 ) ( -940 592 0 ) common/cncr_wall01b -768 -640 0 0.125000 0.125000 0 0 0 +( -940 592 4 ) ( -948 592 4 ) ( -948 608 4 ) common/cncr_wall01b -768 -640 0 0.125000 0.125000 0 0 0 +( -936 608 4 ) ( -948 608 4 ) ( -948 608 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -936 608 0 ) ( -940 596 0 ) ( -940 596 4 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +( -948 604 4 ) ( -940 596 4 ) ( -940 596 0 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +( -948 608 4 ) ( -948 604 4 ) ( -948 604 0 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +} +// brush 74 +{ +( -964 608 0 ) ( -964 592 0 ) ( -956 592 0 ) common/cncr_wall01b -640 -640 0 0.125000 0.125000 0 0 0 +( -956 592 4 ) ( -964 592 4 ) ( -964 608 4 ) common/cncr_wall01b -640 -640 0 0.125000 0.125000 0 0 0 +( -956 604 4 ) ( -960 608 4 ) ( -960 608 0 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +( -956 604 0 ) ( -956 596 0 ) ( -956 596 4 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +( -956 596 0 ) ( -960 600 0 ) ( -960 600 4 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +( -960 608 4 ) ( -960 600 4 ) ( -960 600 0 ) common/cncr_wall01b 640 0 0 0.125000 0.125000 0 0 0 +} +// brush 75 +{ +( -956 640 0 ) ( -964 640 0 ) ( -964 624 0 ) common/cncr_wall01b -640 -384 0 0.125000 0.125000 0 0 0 +( -964 624 4 ) ( -964 640 4 ) ( -956 640 4 ) common/cncr_wall01b -640 -384 0 0.125000 0.125000 0 0 0 +( -960 624 0 ) ( -960 624 4 ) ( -956 628 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -956 636 4 ) ( -956 636 0 ) ( -956 628 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -960 632 4 ) ( -960 632 0 ) ( -956 636 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -960 632 0 ) ( -960 632 4 ) ( -960 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 76 +{ +( -944 640 0 ) ( -960 640 0 ) ( -960 592 0 ) common/cncr_wall01b -640 -384 0 0.125000 0.125000 0 0 0 +( -960 592 4 ) ( -960 640 4 ) ( -944 640 4 ) common/cncr_wall01b -640 -384 0 0.125000 0.125000 0 0 0 +( -940 616 4 ) ( -940 616 0 ) ( -952 616 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -940 616 0 ) ( -940 616 4 ) ( -936 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -948 624 4 ) ( -948 624 0 ) ( -936 624 0 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -952 616 4 ) ( -952 616 0 ) ( -948 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 77 +{ +( -940 640 0 ) ( -948 640 0 ) ( -948 624 0 ) common/cncr_wall01b -768 -384 0 0.125000 0.125000 0 0 0 +( -948 624 4 ) ( -948 640 4 ) ( -940 640 4 ) common/cncr_wall01b -768 -384 0 0.125000 0.125000 0 0 0 +( -948 624 0 ) ( -948 624 4 ) ( -936 624 4 ) common/cncr_wall01b -640 0 0 0.125000 0.125000 0 0 0 +( -940 636 4 ) ( -940 636 0 ) ( -936 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -940 636 0 ) ( -940 636 4 ) ( -948 628 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -948 628 0 ) ( -948 628 4 ) ( -948 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 78 +{ +( -944 652 0 ) ( -952 652 0 ) ( -952 636 0 ) common/cncr_wall01b -736 -288 0 0.125000 0.125000 0 0 0 +( -952 636 4 ) ( -952 652 4 ) ( -944 652 4 ) common/cncr_wall01b -736 -288 0 0.125000 0.125000 0 0 0 +( -948 628 0 ) ( -948 628 4 ) ( -940 636 4 ) common/cncr_wall01b 416 0 0 0.125000 0.125000 0 0 0 +( -948 640 4 ) ( -948 640 0 ) ( -940 636 0 ) common/cncr_wall01b -736 0 0 0.125000 0.125000 0 0 0 +( -952 632 4 ) ( -952 632 0 ) ( -948 640 0 ) common/cncr_wall01b 288 0 0 0.125000 0.125000 0 0 0 +( -952 632 0 ) ( -952 632 4 ) ( -948 628 4 ) common/cncr_wall01b 288 0 0 0.125000 0.125000 0 0 0 +} +// brush 79 +{ +( -948 648 0 ) ( -956 648 0 ) ( -956 632 0 ) common/cncr_wall01b -704 -320 0 0.125000 0.125000 0 0 0 +( -956 632 4 ) ( -956 648 4 ) ( -948 648 4 ) common/cncr_wall01b -704 -320 0 0.125000 0.125000 0 0 0 +( -952 632 0 ) ( -952 632 4 ) ( -948 640 4 ) common/cncr_wall01b 448 0 0 0.125000 0.125000 0 0 0 +( -956 636 4 ) ( -956 636 0 ) ( -948 640 0 ) common/cncr_wall01b -704 0 0 0.125000 0.125000 0 0 0 +( -956 636 0 ) ( -956 636 4 ) ( -956 628 4 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +( -952 632 4 ) ( -952 632 0 ) ( -956 628 0 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +} +// brush 80 +{ +( -1332 648 0 ) ( -1340 648 0 ) ( -1340 632 0 ) common/cncr_wall01b 320 -320 0 0.125000 0.125000 0 0 0 +( -1340 632 4 ) ( -1340 648 4 ) ( -1332 648 4 ) common/cncr_wall01b 320 -320 0 0.125000 0.125000 0 0 0 +( -1336 632 0 ) ( -1336 632 4 ) ( -1332 640 4 ) common/cncr_wall01b 448 0 0 0.125000 0.125000 0 0 0 +( -1340 636 4 ) ( -1340 636 0 ) ( -1332 640 0 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +( -1340 636 0 ) ( -1340 636 4 ) ( -1340 628 4 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +( -1336 632 4 ) ( -1336 632 0 ) ( -1340 628 0 ) common/cncr_wall01b 320 0 0 0.125000 0.125000 0 0 0 +} +// brush 81 +{ +( -1328 652 0 ) ( -1336 652 0 ) ( -1336 636 0 ) common/cncr_wall01b 288 -288 0 0.125000 0.125000 0 0 0 +( -1336 636 4 ) ( -1336 652 4 ) ( -1328 652 4 ) common/cncr_wall01b 288 -288 0 0.125000 0.125000 0 0 0 +( -1332 628 0 ) ( -1332 628 4 ) ( -1324 636 4 ) common/cncr_wall01b 416 0 0 0.125000 0.125000 0 0 0 +( -1332 640 4 ) ( -1332 640 0 ) ( -1324 636 0 ) common/cncr_wall01b 288 0 0 0.125000 0.125000 0 0 0 +( -1336 632 4 ) ( -1336 632 0 ) ( -1332 640 0 ) common/cncr_wall01b 288 0 0 0.125000 0.125000 0 0 0 +( -1336 632 0 ) ( -1336 632 4 ) ( -1332 628 4 ) common/cncr_wall01b 288 0 0 0.125000 0.125000 0 0 0 +} +// brush 82 +{ +( -1324 640 0 ) ( -1332 640 0 ) ( -1332 624 0 ) common/cncr_wall01b 256 -384 0 0.125000 0.125000 0 0 0 +( -1332 624 4 ) ( -1332 640 4 ) ( -1324 640 4 ) common/cncr_wall01b 256 -384 0 0.125000 0.125000 0 0 0 +( -1332 624 0 ) ( -1332 624 4 ) ( -1320 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1324 636 4 ) ( -1324 636 0 ) ( -1320 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1324 636 0 ) ( -1324 636 4 ) ( -1332 628 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1332 628 0 ) ( -1332 628 4 ) ( -1332 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 83 +{ +( -1328 640 0 ) ( -1344 640 0 ) ( -1344 592 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1344 592 4 ) ( -1344 640 4 ) ( -1328 640 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1332 592 4 ) ( -1332 592 0 ) ( -1344 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1332 592 0 ) ( -1332 592 4 ) ( -1320 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1332 624 4 ) ( -1332 624 0 ) ( -1320 624 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1332 624 0 ) ( -1332 624 4 ) ( -1344 592 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 84 +{ +( -1320 604 0 ) ( -1328 604 0 ) ( -1328 592 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1328 592 4 ) ( -1328 604 4 ) ( -1320 604 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1336 592 0 ) ( -1336 592 4 ) ( -1324 592 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1320 592 4 ) ( -1320 604 4 ) ( -1320 604 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1332 604 4 ) ( -1332 604 0 ) ( -1324 604 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1328 604 0 ) ( -1328 604 4 ) ( -1332 592 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 85 +{ +( -1340 640 0 ) ( -1348 640 0 ) ( -1348 624 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1348 624 4 ) ( -1348 640 4 ) ( -1340 640 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1344 624 0 ) ( -1344 624 4 ) ( -1340 628 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1340 636 4 ) ( -1340 636 0 ) ( -1340 628 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1344 632 4 ) ( -1344 632 0 ) ( -1340 636 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1344 632 0 ) ( -1344 632 4 ) ( -1344 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 86 +{ +( -1724 640 0 ) ( -1732 640 0 ) ( -1732 624 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1732 624 4 ) ( -1732 640 4 ) ( -1724 640 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1728 624 0 ) ( -1728 624 4 ) ( -1724 628 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1724 628 0 ) ( -1724 628 4 ) ( -1724 640 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1728 636 4 ) ( -1728 636 0 ) ( -1724 640 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1728 636 0 ) ( -1728 636 4 ) ( -1728 624 4 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 87 +{ +( -1720 604 0 ) ( -1728 604 0 ) ( -1728 592 0 ) common/cncr_wall01b 512 -384 0 0.125000 0.125000 0 0 0 +( -1728 592 4 ) ( -1728 604 4 ) ( -1720 604 4 ) common/cncr_wall01b 512 -384 0 0.125000 0.125000 0 0 0 +( -1728 592 4 ) ( -1720 592 4 ) ( -1720 592 0 ) common/cncr_wall01b 512 0 0 0.125000 0.125000 0 0 0 +( -1724 592 4 ) ( -1724 604 4 ) ( -1724 604 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1720 604 4 ) ( -1728 604 4 ) ( -1728 604 0 ) common/cncr_wall01b 512 0 0 0.125000 0.125000 0 0 0 +( -1728 604 4 ) ( -1728 592 4 ) ( -1728 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 88 +{ +( -1704 604 0 ) ( -1712 604 0 ) ( -1712 592 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1712 592 4 ) ( -1712 604 4 ) ( -1704 604 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1712 592 4 ) ( -1704 592 4 ) ( -1704 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1708 592 4 ) ( -1708 604 4 ) ( -1708 604 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1704 604 4 ) ( -1712 604 4 ) ( -1712 604 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1712 604 4 ) ( -1712 592 4 ) ( -1712 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 89 +{ +( -1712 640 0 ) ( -1728 640 0 ) ( -1728 592 0 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1728 592 4 ) ( -1728 640 4 ) ( -1712 640 4 ) common/cncr_wall01b 384 -384 0 0.125000 0.125000 0 0 0 +( -1728 592 88 ) ( -1712 592 88 ) ( -1712 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1712 592 88 ) ( -1712 640 88 ) ( -1712 640 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1712 640 88 ) ( -1728 640 88 ) ( -1728 640 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +( -1724 640 88 ) ( -1724 592 88 ) ( -1724 592 0 ) common/cncr_wall01b 384 0 0 0.125000 0.125000 0 0 0 +} +// brush 90 +{ +( -248 592 0 ) ( -568 592 0 ) ( -568 336 0 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -248 576 0 ) ( -248 576 224 ) ( -264 576 224 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -568 352 48 ) ( -248 352 48 ) ( -248 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( -568 336 224 ) ( -568 592 224 ) ( -248 592 224 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -264 592 48 ) ( -264 336 48 ) ( -264 592 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -248 576 224 ) ( -248 576 0 ) ( -248 352 0 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +} +// brush 91 +{ +( -704 336 48 ) ( -384 336 48 ) ( -384 336 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -256 320 48 ) ( -256 576 48 ) ( -256 576 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -640 592 48 ) ( -640 336 48 ) ( -640 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -64 352 48 ) ( -384 352 48 ) ( -64 352 -16 ) common/cncr_wall01b 0 0 0 0.125000 0.125000 0 0 0 +( -640 336 224 ) ( -640 592 224 ) ( -320 592 224 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +( -128 336 0 ) ( -64 336 0 ) ( -96 352 0 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +} +// brush 92 +{ +( -1400 464 48 ) ( -1400 720 48 ) ( -1400 720 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1416 592 48 ) ( -1416 336 48 ) ( -1416 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1720 336 224 ) ( -1720 592 224 ) ( -1400 592 224 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -1400 592 0 ) ( -1720 592 0 ) ( -1720 336 0 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -1720 352 48 ) ( -1400 352 48 ) ( -1400 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( -1416 576 0 ) ( -1384 576 0 ) ( -1400 576 224 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +} +// brush 93 +{ +( -1016 464 48 ) ( -1016 720 48 ) ( -1016 720 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1032 592 48 ) ( -1032 336 48 ) ( -1032 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1336 336 224 ) ( -1336 592 224 ) ( -1016 592 224 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -1016 592 0 ) ( -1336 592 0 ) ( -1336 336 0 ) common/asphault -64 512 0 0.125000 0.125000 0 0 0 +( -1336 352 48 ) ( -1016 352 48 ) ( -1016 352 -16 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +( -1048 576 0 ) ( -984 576 0 ) ( -1016 576 224 ) common/asphault -64 -128 0 0.125000 0.125000 0 0 0 +} +// brush 94 +{ +( -632 464 48 ) ( -632 720 48 ) ( -632 720 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -648 592 48 ) ( -648 336 48 ) ( -648 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -952 336 224 ) ( -952 592 224 ) ( -632 592 224 ) common/asphault 960 512 0 0.125000 0.125000 0 0 0 +( -632 592 0 ) ( -952 592 0 ) ( -952 336 0 ) common/asphault 960 512 0 0.125000 0.125000 0 0 0 +( -952 352 48 ) ( -632 352 48 ) ( -632 352 -16 ) common/asphault 960 -128 0 0.125000 0.125000 0 0 0 +( -664 576 0 ) ( -616 576 0 ) ( -640 576 224 ) common/asphault 960 -128 0 0.125000 0.125000 0 0 0 +} +// brush 95 +{ +( -1536 592 -16 ) ( -1856 592 -16 ) ( -1856 336 -16 ) common/asphault 0 640 0 0.125000 0.125000 0 0 0 +( -1856 336 48 ) ( -1536 336 48 ) ( -1536 336 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1408 336 48 ) ( -1408 592 48 ) ( -1408 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1536 720 48 ) ( -1856 720 48 ) ( -1856 720 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1792 592 48 ) ( -1792 336 48 ) ( -1792 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1856 592 0 ) ( -1536 592 0 ) ( -1856 336 0 ) common/asphault 0 640 0 0.125000 0.125000 0 0 0 +} +// brush 96 +{ +( -1856 336 240 ) ( -1856 592 240 ) ( -1536 592 240 ) common/asphault 0 640 0 0.125000 0.125000 0 0 0 +( -1856 336 48 ) ( -1536 336 48 ) ( -1536 336 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1408 336 48 ) ( -1408 592 48 ) ( -1408 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1536 720 48 ) ( -1856 720 48 ) ( -1856 720 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1792 592 48 ) ( -1792 336 48 ) ( -1792 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1856 592 224 ) ( -1856 336 224 ) ( -1536 592 224 ) common/asphault 0 640 0 0.125000 0.125000 0 0 0 +} +// brush 97 +{ +( -1728 336 48 ) ( -1408 336 48 ) ( -1408 336 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1408 336 48 ) ( -1408 592 48 ) ( -1408 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1792 592 48 ) ( -1792 336 48 ) ( -1792 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1536 352 48 ) ( -1856 352 48 ) ( -1536 352 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1856 336 224 ) ( -1856 592 224 ) ( -1536 592 224 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +( -1536 592 0 ) ( -1856 592 0 ) ( -1856 336 0 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +} +// brush 98 +{ +( -1408 464 48 ) ( -1408 720 48 ) ( -1408 720 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1536 720 48 ) ( -1856 720 48 ) ( -1856 720 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1792 720 48 ) ( -1792 464 48 ) ( -1792 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1728 704 48 ) ( -1408 704 48 ) ( -1728 704 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1856 464 224 ) ( -1856 720 224 ) ( -1536 720 224 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +( -1536 720 0 ) ( -1856 720 0 ) ( -1856 464 0 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +} +// brush 99 +{ +( -1792 592 48 ) ( -1792 336 48 ) ( -1792 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1776 464 48 ) ( -1776 720 48 ) ( -1776 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1856 336 224 ) ( -1856 592 224 ) ( -1536 592 224 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +( -1536 592 0 ) ( -1856 592 0 ) ( -1856 336 0 ) common/asphault 0 512 0 0.125000 0.125000 0 0 0 +( -1856 352 48 ) ( -1536 352 48 ) ( -1536 352 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +( -1536 704 48 ) ( -1856 704 48 ) ( -1856 704 -16 ) common/asphault 0 -128 0 0.125000 0.125000 0 0 0 +} +// brush 100 +{ +( 1792 552 0 ) ( 1744 552 0 ) ( 1744 504 0 ) tools/_clip -64 128 0 0.125000 0.125000 0 0 0 +( 1744 504 48 ) ( 1744 552 48 ) ( 1792 552 48 ) tools/_clip -64 128 0 0.125000 0.125000 0 0 0 +( 1744 508 40 ) ( 1792 508 40 ) ( 1792 508 0 ) tools/_clip -64 0 0 0.125000 0.125000 0 0 0 +( 1788 504 40 ) ( 1788 552 40 ) ( 1788 552 0 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1792 548 40 ) ( 1744 548 40 ) ( 1744 548 0 ) tools/_clip -64 0 0 0.125000 0.125000 0 0 0 +( 1748 552 40 ) ( 1748 504 40 ) ( 1748 504 0 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1748 532 0 ) ( 1764 548 0 ) ( 1756 540 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1772 548 0 ) ( 1788 532 0 ) ( 1780 540 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1764 508 0 ) ( 1748 524 0 ) ( 1756 516 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1788 524 0 ) ( 1772 508 0 ) ( 1780 516 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +} +// brush 101 +{ +( 1792 552 0 ) ( 1744 552 0 ) ( 1744 504 0 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +( 1752 512 42 ) ( 1784 512 42 ) ( 1780 516 0 ) tools/_shoot_metal -64 0 0 0.125000 0.125000 0 0 0 +( 1752 540 42 ) ( 1752 516 42 ) ( 1756 516 0 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1784 516 42 ) ( 1784 540 42 ) ( 1780 540 0 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1784 544 42 ) ( 1752 544 42 ) ( 1756 540 0 ) tools/_shoot_metal -64 0 0 0.125000 0.125000 0 0 0 +( 1766 512 0 ) ( 1752 526 0 ) ( 1759 519 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1752 530 0 ) ( 1766 544 0 ) ( 1759 537 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1772 544 0 ) ( 1784 530 0 ) ( 1778 537 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1784 526 0 ) ( 1770 512 0 ) ( 1777 519 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1768 511 46 ) ( 1752 511 42 ) ( 1760 545 44 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +( 1784 512 42 ) ( 1768 512 46 ) ( 1776 544 44 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +} +// brush 102 +{ +( 1984 552 0 ) ( 1936 552 0 ) ( 1936 504 0 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +( 1944 512 42 ) ( 1976 512 42 ) ( 1972 516 0 ) tools/_shoot_metal -64 0 0 0.125000 0.125000 0 0 0 +( 1944 540 42 ) ( 1944 516 42 ) ( 1948 516 0 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1976 516 42 ) ( 1976 540 42 ) ( 1972 540 0 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1976 544 42 ) ( 1944 544 42 ) ( 1948 540 0 ) tools/_shoot_metal -64 0 0 0.125000 0.125000 0 0 0 +( 1958 512 0 ) ( 1944 526 0 ) ( 1951 519 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1944 530 0 ) ( 1958 544 0 ) ( 1951 537 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1964 544 0 ) ( 1976 530 0 ) ( 1970 537 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1976 526 0 ) ( 1962 512 0 ) ( 1969 519 42 ) tools/_shoot_metal -128 0 0 0.125000 0.125000 0 0 0 +( 1960 511 46 ) ( 1944 511 42 ) ( 1952 545 44 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +( 1976 512 42 ) ( 1960 512 46 ) ( 1968 544 44 ) tools/_shoot_metal -64 128 0 0.125000 0.125000 0 0 0 +} +// brush 103 +{ +( 1984 552 0 ) ( 1936 552 0 ) ( 1936 504 0 ) tools/_clip -64 128 0 0.125000 0.125000 0 0 0 +( 1936 504 48 ) ( 1936 552 48 ) ( 1984 552 48 ) tools/_clip -64 128 0 0.125000 0.125000 0 0 0 +( 1936 508 40 ) ( 1984 508 40 ) ( 1984 508 0 ) tools/_clip -64 0 0 0.125000 0.125000 0 0 0 +( 1980 504 40 ) ( 1980 552 40 ) ( 1980 552 0 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1984 548 40 ) ( 1936 548 40 ) ( 1936 548 0 ) tools/_clip -64 0 0 0.125000 0.125000 0 0 0 +( 1940 552 40 ) ( 1940 504 40 ) ( 1940 504 0 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1940 532 0 ) ( 1956 548 0 ) ( 1948 540 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1964 548 0 ) ( 1980 532 0 ) ( 1972 540 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1956 508 0 ) ( 1940 524 0 ) ( 1948 516 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +( 1980 524 0 ) ( 1964 508 0 ) ( 1972 516 40 ) tools/_clip -128 0 0 0.125000 0.125000 0 0 0 +} +// brush 104 +{ +( -1088 592 -16 ) ( -1408 592 -16 ) ( -1408 336 -16 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( -1408 336 48 ) ( -1088 336 48 ) ( -1088 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1024 336 48 ) ( -1024 592 48 ) ( -1024 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1088 720 48 ) ( -1408 720 48 ) ( -1408 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1408 592 48 ) ( -1408 336 48 ) ( -1408 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1408 592 0 ) ( -1088 592 0 ) ( -1408 336 0 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 105 +{ +( -1408 336 240 ) ( -1408 592 240 ) ( -1088 592 240 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +( -1408 336 48 ) ( -1088 336 48 ) ( -1088 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1024 336 48 ) ( -1024 592 48 ) ( -1024 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1088 720 48 ) ( -1408 720 48 ) ( -1408 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1408 592 48 ) ( -1408 336 48 ) ( -1408 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1408 592 224 ) ( -1408 336 224 ) ( -1088 592 224 ) common/asphault -512 640 0 0.125000 0.125000 0 0 0 +} +// brush 106 +{ +( -1408 336 48 ) ( -1088 336 48 ) ( -1088 336 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1024 336 48 ) ( -1024 592 48 ) ( -1024 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1408 592 48 ) ( -1408 336 48 ) ( -1408 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1152 352 48 ) ( -1472 352 48 ) ( -1152 352 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1408 336 224 ) ( -1408 592 224 ) ( -1088 592 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( -1088 592 0 ) ( -1408 592 0 ) ( -1408 336 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 107 +{ +( -1024 464 48 ) ( -1024 720 48 ) ( -1024 720 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1088 720 48 ) ( -1408 720 48 ) ( -1408 720 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1408 720 48 ) ( -1408 464 48 ) ( -1408 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1344 704 48 ) ( -1024 704 48 ) ( -1344 704 -16 ) common/asphault -512 -128 0 0.125000 0.125000 0 0 0 +( -1408 464 224 ) ( -1408 720 224 ) ( -1088 720 224 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +( -1088 720 0 ) ( -1408 720 0 ) ( -1408 464 0 ) common/asphault -512 512 0 0.125000 0.125000 0 0 0 +} +// brush 108 +{ +( -1168 544 0 ) ( -1200 544 0 ) ( -1200 512 0 ) common/asphault1 -768 0 0 0.125000 0.125000 0 0 0 +( -1196 512 4 ) ( -1196 544 4 ) ( -1164 544 4 ) common/asphault1 -768 0 0 0.125000 0.125000 0 0 0 +( -1200 512 0 ) ( -1168 512 0 ) ( -1168 512 -16 ) common/asphault1 -768 0 0 0.125000 0.125000 0 0 0 +( -1168 512 0 ) ( -1168 544 0 ) ( -1168 544 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +( -1168 544 0 ) ( -1200 544 0 ) ( -1200 544 -16 ) common/asphault1 -768 0 0 0.125000 0.125000 0 0 0 +( -1200 544 0 ) ( -1200 512 0 ) ( -1200 512 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +} +// brush 109 +{ +( -704 592 -16 ) ( -1024 592 -16 ) ( -1024 336 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -1024 336 48 ) ( -704 336 48 ) ( -704 336 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 320 48 ) ( -640 576 48 ) ( -640 576 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -704 720 48 ) ( -1024 720 48 ) ( -1024 720 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -1024 592 48 ) ( -1024 336 48 ) ( -1024 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1024 592 0 ) ( -704 592 0 ) ( -1024 336 0 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +} +// brush 110 +{ +( -1024 336 240 ) ( -1024 592 240 ) ( -704 592 240 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -1024 336 48 ) ( -704 336 48 ) ( -704 336 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 336 48 ) ( -640 592 48 ) ( -640 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -704 720 48 ) ( -1024 720 48 ) ( -1024 720 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -1024 592 48 ) ( -1024 336 48 ) ( -1024 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1024 592 224 ) ( -1024 336 224 ) ( -704 592 224 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +} +// brush 111 +{ +( -1024 336 48 ) ( -704 336 48 ) ( -704 336 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 336 48 ) ( -640 592 48 ) ( -640 592 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -1024 592 48 ) ( -1024 336 48 ) ( -1024 336 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -768 352 48 ) ( -1088 352 48 ) ( -768 352 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -1024 336 224 ) ( -1024 592 224 ) ( -704 592 224 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +( -704 592 0 ) ( -1024 592 0 ) ( -1024 336 0 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +} +// brush 112 +{ +( -640 456 48 ) ( -640 712 48 ) ( -640 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -640 720 48 ) ( -960 720 48 ) ( -960 720 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -1024 720 48 ) ( -1024 464 48 ) ( -1024 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -960 704 48 ) ( -640 704 48 ) ( -960 704 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -1024 464 224 ) ( -1024 720 224 ) ( -704 720 224 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +( -704 720 0 ) ( -1024 720 0 ) ( -1024 464 0 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +} +// brush 113 +{ +( -816 544 0 ) ( -848 544 0 ) ( -848 512 0 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -844 512 4 ) ( -844 544 4 ) ( -812 544 4 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -848 512 0 ) ( -816 512 0 ) ( -816 512 -16 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -816 512 0 ) ( -816 544 0 ) ( -816 544 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +( -816 544 0 ) ( -848 544 0 ) ( -848 544 -16 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -848 544 0 ) ( -848 512 0 ) ( -848 512 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +} +// brush 114 +{ +( -432 544 0 ) ( -464 544 0 ) ( -464 512 0 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -460 512 4 ) ( -460 544 4 ) ( -428 544 4 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -464 512 0 ) ( -432 512 0 ) ( -432 512 -16 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -432 512 0 ) ( -432 544 0 ) ( -432 544 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +( -432 544 0 ) ( -464 544 0 ) ( -464 544 -16 ) common/asphault1 512 0 0 0.125000 0.125000 0 0 0 +( -464 544 0 ) ( -464 512 0 ) ( -464 512 -16 ) common/asphault1 0 0 0 0.125000 0.125000 0 0 0 +} +// brush 115 +{ +( -640 -112 240 ) ( -640 144 240 ) ( -320 144 240 ) common/asphault 512 128 0 0.125000 0.125000 0 0 0 +( -680 336 48 ) ( -360 336 48 ) ( -360 336 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -256 -112 48 ) ( -256 144 48 ) ( -256 144 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( -256 720 48 ) ( -576 720 48 ) ( -576 720 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 144 48 ) ( -640 -112 48 ) ( -640 -112 -16 ) common/asphault -128 -128 0 0.125000 0.125000 0 0 0 +( -640 144 224 ) ( -640 -112 224 ) ( -320 144 224 ) common/asphault 512 128 0 0.125000 0.125000 0 0 0 +} +// brush 116 +{ +( -256 456 48 ) ( -256 712 48 ) ( -256 712 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -320 720 48 ) ( -640 720 48 ) ( -640 720 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 720 48 ) ( -640 464 48 ) ( -640 464 -16 ) common/asphault -640 -128 0 0.125000 0.125000 0 0 0 +( -384 704 48 ) ( -64 704 48 ) ( -384 704 -16 ) common/asphault 512 -128 0 0.125000 0.125000 0 0 0 +( -640 464 224 ) ( -640 720 224 ) ( -320 720 224 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +( -320 720 0 ) ( -640 720 0 ) ( -640 464 0 ) common/asphault 512 512 0 0.125000 0.125000 0 0 0 +} +// brush 117 +{ +( -640 144 48 ) ( -640 -112 48 ) ( -640 -112 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -640 144 0 ) ( -320 144 0 ) ( -640 -112 0 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -320 720 48 ) ( -640 720 48 ) ( -640 720 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -256 -112 48 ) ( -256 144 48 ) ( -256 144 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -680 336 48 ) ( -360 336 48 ) ( -360 336 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +( -320 144 -16 ) ( -640 144 -16 ) ( -640 -112 -16 ) common/asphault 512 640 0 0.125000 0.125000 0 0 0 +} +// brush 118 +{ +( -40 550 0 ) ( -86 550 0 ) ( -86 506 0 ) tools/_shoot_metal -192 -192 0 0.125000 0.125000 0 0 0 +( -86 506 64 ) ( -86 550 64 ) ( -40 550 64 ) tools/_shoot_metal -192 -192 0 0.125000 0.125000 0 0 0 +( -86 506 48 ) ( -40 506 48 ) ( -40 506 40 ) tools/_shoot_metal -192 0 0 0.125000 0.125000 0 0 0 +( -42 506 48 ) ( -42 550 48 ) ( -42 550 40 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +( -40 550 48 ) ( -86 550 48 ) ( -86 550 40 ) tools/_shoot_metal -192 0 0 0.125000 0.125000 0 0 0 +( -86 550 48 ) ( -86 506 48 ) ( -86 506 40 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +( -88 536 40 ) ( -72 552 40 ) ( -80 544 64 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +( -56 552 40 ) ( -40 536 40 ) ( -48 544 64 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +( -72 504 40 ) ( -88 520 40 ) ( -80 512 64 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +( -40 520 40 ) ( -56 504 40 ) ( -48 512 64 ) tools/_shoot_metal 192 0 0 0.125000 0.125000 0 0 0 +} +// brush 119 + { + patchDef2 + { + kamchatka/railing01_2sided + ( 3 3 0 0 0 ) +( +( ( 1088 452 44 0 0 ) ( 1088 452 22 0 0.343750 ) ( 1088 452 0 0 0.687500 ) ) +( ( 1088 512 44 0.937500 0 ) ( 1088 512 22 0.937500 0.343750 ) ( 1088 512 0 0.937500 0.687500 ) ) +( ( 1088 572 44 1.875000 0 ) ( 1088 572 22 1.875000 0.343750 ) ( 1088 572 0 1.875000 0.687500 ) ) +) + } + } +// brush 120 +{ +( 1090 572 0 ) ( 1090 576 0 ) ( 1086 576 0 ) tools/_caulk 0 -81 90 0.125000 0.125000 134217728 0 0 +( 1086 576 44 ) ( 1086 572 44 ) ( 1086 572 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 1070 572 44 ) ( 1074 572 44 ) ( 1074 572 0 ) kamchatka/sup_beam01b 337 -255 -180 0.125000 -0.125000 134217728 0 0 +( 1090 572 44 ) ( 1090 576 44 ) ( 1090 576 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 1090 576 44 ) ( 1086 576 44 ) ( 1086 576 0 ) kamchatka/sup_beam01b 337 -255 -180 0.125000 -0.125000 134217728 0 0 +( 1086 572 44 ) ( 1086 576 48 ) ( 1090 574 46 ) kamchatka/sup_beam01b 336 -191 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 121 +{ +( 1090 448 0 ) ( 1090 452 0 ) ( 1086 452 0 ) tools/_caulk 224 -81 90 0.125000 0.125000 134217728 0 0 +( 1086 452 44 ) ( 1086 448 44 ) ( 1086 448 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 1070 448 44 ) ( 1074 448 44 ) ( 1074 448 0 ) kamchatka/sup_beam01b 337 -255 -180 0.125000 -0.125000 134217728 0 0 +( 1090 448 44 ) ( 1090 452 44 ) ( 1090 452 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 1090 452 44 ) ( 1086 452 44 ) ( 1086 452 0 ) kamchatka/sup_beam01b 337 -255 -180 0.125000 -0.125000 134217728 0 0 +( 1086 448 48 ) ( 1086 452 44 ) ( 1090 450 46 ) kamchatka/sup_beam01b 336 -191 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 122 +{ +( 1094 504 44 ) ( 1094 540 44 ) ( 1086 540 44 ) kamchatka/sup_beam01b 0 -337 90 0.125000 0.125000 134217728 0 0 +( 1086 540 48 ) ( 1094 540 48 ) ( 1094 504 48 ) kamchatka/sup_beam01b 0 -337 90 0.125000 0.125000 134217728 0 0 +( 1086 540 48 ) ( 1086 504 48 ) ( 1086 504 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 1090 504 48 ) ( 1090 540 48 ) ( 1090 540 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 1086 576 48 ) ( 1086 572 44 ) ( 1090 574 46 ) kamchatka/sup_beam01b 289 -1 -180 0.125000 -0.125000 134217728 0 0 +( 1086 452 44 ) ( 1086 448 48 ) ( 1090 450 46 ) kamchatka/sup_beam01b 289 -1 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 123 +{ +( 710 504 44 ) ( 710 540 44 ) ( 702 540 44 ) kamchatka/sup_beam01b -1 173 90 0.125000 0.125000 134217728 0 0 +( 702 540 48 ) ( 710 540 48 ) ( 710 504 48 ) kamchatka/sup_beam01b -1 173 90 0.125000 0.125000 134217728 0 0 +( 702 540 48 ) ( 702 504 48 ) ( 702 504 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 706 504 48 ) ( 706 540 48 ) ( 706 540 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 702 576 48 ) ( 702 572 44 ) ( 706 574 46 ) kamchatka/sup_beam01b -222 -1 -180 0.125000 -0.125000 134217728 0 0 +( 702 452 44 ) ( 702 448 48 ) ( 706 450 46 ) kamchatka/sup_beam01b -222 -1 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 124 +{ +( 706 448 0 ) ( 706 452 0 ) ( 702 452 0 ) tools/_caulk -33 -82 90 0.125000 0.125000 134217728 0 0 +( 702 452 44 ) ( 702 448 44 ) ( 702 448 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 686 448 44 ) ( 690 448 44 ) ( 690 448 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 706 448 44 ) ( 706 452 44 ) ( 706 452 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 706 452 44 ) ( 702 452 44 ) ( 702 452 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 702 448 48 ) ( 702 452 44 ) ( 706 450 46 ) kamchatka/sup_beam01b -175 -190 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 125 +{ +( 706 572 0 ) ( 706 576 0 ) ( 702 576 0 ) tools/_caulk -1 -82 90 0.125000 0.125000 134217728 0 0 +( 702 576 44 ) ( 702 572 44 ) ( 702 572 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 686 572 44 ) ( 690 572 44 ) ( 690 572 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 706 572 44 ) ( 706 576 44 ) ( 706 576 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 706 576 44 ) ( 702 576 44 ) ( 702 576 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 702 572 44 ) ( 702 576 48 ) ( 706 574 46 ) kamchatka/sup_beam01b -175 -190 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 126 +{ +( 326 504 44 ) ( 326 540 44 ) ( 318 540 44 ) kamchatka/sup_beam01b 511 174 90 0.125000 0.125000 134217728 0 0 +( 318 540 48 ) ( 326 540 48 ) ( 326 504 48 ) kamchatka/sup_beam01b 511 174 90 0.125000 0.125000 134217728 0 0 +( 318 540 48 ) ( 318 504 48 ) ( 318 504 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 322 512 48 ) ( 322 548 48 ) ( 322 548 44 ) kamchatka/sup_beam01b 0 -64 0 0.125000 0.125000 134217728 0 0 +( 318 576 48 ) ( 318 572 44 ) ( 322 574 46 ) kamchatka/sup_beam01b -222 -1 -180 0.125000 -0.125000 134217728 0 0 +( 318 452 44 ) ( 318 448 48 ) ( 322 450 46 ) kamchatka/sup_beam01b -222 -1 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 127 +{ +( 322 456 0 ) ( 322 460 0 ) ( 318 460 0 ) tools/_caulk 223 -82 90 0.125000 0.125000 134217728 0 0 +( 318 452 44 ) ( 318 448 44 ) ( 318 448 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 302 448 44 ) ( 306 448 44 ) ( 306 448 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 322 456 44 ) ( 322 460 44 ) ( 322 460 0 ) kamchatka/sup_beam01b 320 -256 0 0.125000 0.125000 134217728 0 0 +( 322 452 44 ) ( 318 452 44 ) ( 318 452 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 318 448 48 ) ( 318 452 44 ) ( 322 450 46 ) kamchatka/sup_beam01b -175 -190 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 128 +{ +( 322 580 0 ) ( 322 584 0 ) ( 318 584 0 ) tools/_caulk 255 -82 90 0.125000 0.125000 134217728 0 0 +( 318 576 44 ) ( 318 572 44 ) ( 318 572 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 302 572 44 ) ( 306 572 44 ) ( 306 572 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 322 580 44 ) ( 322 584 44 ) ( 322 584 0 ) kamchatka/sup_beam01b 352 -256 0 0.125000 0.125000 134217728 0 0 +( 322 576 44 ) ( 318 576 44 ) ( 318 576 0 ) kamchatka/sup_beam01b -175 -254 -180 0.125000 -0.125000 134217728 0 0 +( 318 572 44 ) ( 318 576 48 ) ( 322 574 46 ) kamchatka/sup_beam01b -175 -190 -180 0.125000 -0.125000 134217728 0 0 +} +// brush 129 +{ +( 708 570 0 ) ( 702 570 0 ) ( 702 544 0 ) tools/_nodraw 128 -61 0 -0.015625 0.468750 134217728 0 0 +( 702 544 44 ) ( 702 570 44 ) ( 708 570 44 ) tools/_nodraw 128 -61 0 -0.015625 0.468750 134217728 0 0 +( 702 452 52 ) ( 708 452 52 ) ( 708 452 4 ) tools/_nodraw 128 0 0 -0.015625 0.171875 134217728 0 0 +( 706 544 52 ) ( 706 570 52 ) ( 706 570 4 ) tools/_nodraw -64 32 0 0.125000 0.125000 134217728 0 0 +( 708 572 52 ) ( 702 572 52 ) ( 702 572 4 ) tools/_nodraw 128 0 0 -0.015625 0.171875 134217728 0 0 +( 702 570 52 ) ( 702 544 52 ) ( 702 544 4 ) kamchatka/railing01_2sided -64 0 0 0.125000 0.125000 134217728 0 0 +} +// brush 130 +{ +( 324 578 0 ) ( 318 578 0 ) ( 318 552 0 ) tools/_caulk -3 -120 0 -0.007813 0.234375 134217728 0 0 +( 318 544 44 ) ( 318 570 44 ) ( 324 570 44 ) tools/_caulk -3 -120 0 -0.007813 0.234375 134217728 0 0 +( 318 452 52 ) ( 324 452 52 ) ( 324 452 4 ) tools/_caulk -3 0 0 -0.007813 0.085938 134217728 0 0 +( 322 552 52 ) ( 322 578 52 ) ( 322 578 4 ) tools/_caulk 192 32 0 0.125000 0.125000 134217728 0 0 +( 324 572 52 ) ( 318 572 52 ) ( 318 572 4 ) tools/_caulk -3 0 0 -0.007813 0.085938 134217728 0 0 +( 318 570 52 ) ( 318 544 52 ) ( 318 544 4 ) kamchatka/railing01_2sided 448 32 0 0.125000 0.125000 134217728 0 0 +} +// brush 131 +{ +( 708 570 0 ) ( 702 570 0 ) ( 702 544 0 ) tools/_clip -192 64 0 0.125000 0.125000 134217728 0 0 +( 702 544 44 ) ( 702 570 44 ) ( 708 570 44 ) tools/_clip -192 64 0 0.125000 0.125000 134217728 0 0 +( 702 452 52 ) ( 708 452 52 ) ( 708 452 4 ) tools/_clip -192 0 0 0.125000 0.125000 134217728 0 0 +( 706 544 52 ) ( 706 570 52 ) ( 706 570 4 ) tools/_clip -64 0 0 0.125000 0.125000 134217728 0 0 +( 708 572 52 ) ( 702 572 52 ) ( 702 572 4 ) tools/_clip -192 0 0 0.125000 0.125000 134217728 0 0 +( 702 570 52 ) ( 702 544 52 ) ( 702 544 4 ) tools/_clip -64 0 0 0.125000 0.125000 134217728 0 0 +} +// brush 132 +{ +( 1092 570 0 ) ( 1086 570 0 ) ( 1086 544 0 ) tools/_clip -192 -192 0 0.125000 0.125000 134217728 0 0 +( 1086 544 44 ) ( 1086 570 44 ) ( 1092 570 44 ) tools/_clip -192 -192 0 0.125000 0.125000 134217728 0 0 +( 1086 452 52 ) ( 1092 452 52 ) ( 1092 452 4 ) tools/_clip -192 0 0 0.125000 0.125000 134217728 0 0 +( 1090 544 52 ) ( 1090 570 52 ) ( 1090 570 4 ) tools/_clip 192 0 0 0.125000 0.125000 134217728 0 0 +( 1092 572 52 ) ( 1086 572 52 ) ( 1086 572 4 ) tools/_clip -192 0 0 0.125000 0.125000 134217728 0 0 +( 1086 570 52 ) ( 1086 544 52 ) ( 1086 544 4 ) tools/_clip 192 0 0 0.125000 0.125000 134217728 0 0 +} +// brush 133 +{ +( 324 578 0 ) ( 318 578 0 ) ( 318 552 0 ) tools/_clip 64 -192 0 0.125000 0.125000 134217728 0 0 +( 318 544 44 ) ( 318 570 44 ) ( 324 570 44 ) tools/_clip 64 -192 0 0.125000 0.125000 134217728 0 0 +( 318 452 52 ) ( 324 452 52 ) ( 324 452 4 ) tools/_clip 64 0 0 0.125000 0.125000 134217728 0 0 +( 322 296 52 ) ( 322 322 52 ) ( 322 322 4 ) tools/_clip 192 0 0 0.125000 0.125000 134217728 0 0 +( 324 572 52 ) ( 318 572 52 ) ( 318 572 4 ) tools/_clip 64 0 0 0.125000 0.125000 134217728 0 0 +( 318 570 52 ) ( 318 544 52 ) ( 318 544 4 ) tools/_clip 192 0 0 0.125000 0.125000 134217728 0 0 +} +} +// entity 1 +{ +"origin" "351 655 15" +"classname" "pickup_weapon_M4" +} +// entity 2 +{ +"origin" "-1663 465 46" +"angles" "0 45 0" +"classname" "info_player_deathmatch" +} +// entity 3 +{ +"origin" "487 655 15" +"classname" "pickup_weapon_SMOHG92" +} +// entity 4 +{ +"origin" "-1504 544 46" +"angles" "0 180 0" +"classname" "gametype_player" +} +// entity 5 +{ +"origin" "-1664 544 46" +"angles" "0 360 0" +"classname" "info_player_deathmatch" +} +// entity 6 +{ +"gametype" "inf" +"origin" "-1584 624 46" +"angles" "0 270 0" +"classname" "gametype_player" +} +// entity 7 +{ +"gametype" "dm inf" +"origin" "-1584 464 46" +"angles" "0 90 0" +"classname" "gametype_player" +} +// entity 8 +{ +"classname" "fx_play_effect" +"effect" "kam1_barrel" +"count" "-1" +"origin" "-64 528 44" +} +// entity 9 +{ +"classname" "func_group" +// brush 0 + { + patchDef2 + { + instances/barrel + ( 9 3 0 0 0 ) +( +( ( -88 528 0 0 0 ) ( -88 528 32 0 0.500000 ) ( -88 528 64 0 1 ) ) +( ( -88 504 0 0.375000 0 ) ( -88 504 32 0.375000 0.500000 ) ( -88 504 64 0.375000 1 ) ) +( ( -64 504 0 0.750000 0 ) ( -64 504 32 0.750000 0.500000 ) ( -64 504 64 0.750000 1 ) ) +( ( -40 504 0 1.125000 0 ) ( -40 504 32 1.125000 0.500000 ) ( -40 504 64 1.125000 1 ) ) +( ( -40 528 0 1.500000 0 ) ( -40 528 32 1.500000 0.500000 ) ( -40 528 64 1.500000 1 ) ) +( ( -40 552 0 1.875000 0 ) ( -40 552 32 1.875000 0.500000 ) ( -40 552 64 1.875000 1 ) ) +( ( -64 552 0 2.250000 0 ) ( -64 552 32 2.250000 0.500000 ) ( -64 552 64 2.250000 1 ) ) +( ( -88 552 0 2.625000 0 ) ( -88 552 32 2.625000 0.500000 ) ( -88 552 64 2.625000 1 ) ) +( ( -88 528 0 3 0 ) ( -88 528 32 3 0.500000 ) ( -88 528 64 3 1 ) ) +) + } + } +// brush 1 + { + patchDef2 + { + instances/barrel + ( 9 3 0 0 0 ) +( +( ( -86 528 0 0 0 ) ( -87 528 0 0 0.015625 ) ( -88 528 0 0 0.031250 ) ) +( ( -86.585938 505.414185 0 0.353022 0 ) ( -87.292969 504.707092 0 0.353022 0.015625 ) ( -88 504 0 0.353022 0.031250 ) ) +( ( -64 506 0 0.706046 0 ) ( -64 505 0 0.706046 0.015625 ) ( -64 504 0 0.706046 0.031250 ) ) +( ( -41.414063 505.414185 0 1.059070 0 ) ( -40.707031 504.707092 0 1.059070 0.015625 ) ( -40 504 0 1.059070 0.031250 ) ) +( ( -42 528 0 1.412091 0 ) ( -41 528 0 1.412091 0.015625 ) ( -40 528 0 1.412091 0.031250 ) ) +( ( -41.414063 550.585815 0 1.765113 0 ) ( -40.707031 551.292908 0 1.765113 0.015625 ) ( -40 552 0 1.765113 0.031250 ) ) +( ( -64 550 0 2.118137 0 ) ( -64 551 0 2.118137 0.015625 ) ( -64 552 0 2.118137 0.031250 ) ) +( ( -86.585938 550.585815 0 2.471161 0 ) ( -87.292969 551.292908 0 2.471161 0.015625 ) ( -88 552 0 2.471161 0.031250 ) ) +( ( -86 528 0 2.824182 0 ) ( -87 528 0 2.824182 0.015625 ) ( -88 528 0 2.824182 0.031250 ) ) +) + } + } +// brush 2 + { + patchDef2 + { + instances/barrel + ( 9 3 0 0 0 ) +( +( ( -86 528 64 0 0 ) ( -86 528 32 0 0.500000 ) ( -86 528 0 0 1 ) ) +( ( -86.585938 505.414185 64 0.353022 0 ) ( -86.585938 505.414185 32 0.353022 0.500000 ) ( -86.585938 505.414185 0 0.353022 1 ) ) +( ( -64 506 64 0.706046 0 ) ( -64 506 32 0.706046 0.500000 ) ( -64 506 0 0.706046 1 ) ) +( ( -41.414063 505.414185 64 1.059070 0 ) ( -41.414063 505.414185 32 1.059070 0.500000 ) ( -41.414063 505.414185 0 1.059070 1 ) ) +( ( -42 528 64 1.412091 0 ) ( -42 528 32 1.412091 0.500000 ) ( -42 528 0 1.412091 1 ) ) +( ( -41.414063 550.585815 64 1.765113 0 ) ( -41.414063 550.585815 32 1.765113 0.500000 ) ( -41.414063 550.585815 0 1.765113 1 ) ) +( ( -64 550 64 2.118137 0 ) ( -64 550 32 2.118137 0.500000 ) ( -64 550 0 2.118137 1 ) ) +( ( -86.585938 550.585815 64 2.471161 0 ) ( -86.585938 550.585815 32 2.471161 0.500000 ) ( -86.585938 550.585815 0 2.471161 1 ) ) +( ( -86 528 64 2.824182 0 ) ( -86 528 32 2.824182 0.500000 ) ( -86 528 0 2.824182 1 ) ) +) + } + } +// brush 3 + { + patchDef2 + { + instances/barrel + ( 9 3 0 0 0 ) +( +( ( -88 528 64 0 0 ) ( -87 528 64 0 0.015625 ) ( -86 528 64 0 0.031250 ) ) +( ( -88 504 64 0.375000 0 ) ( -87.292969 504.707092 64 0.375000 0.015625 ) ( -86.585938 505.414185 64 0.375000 0.031250 ) ) +( ( -64 504 64 0.750000 0 ) ( -64 505 64 0.750000 0.015625 ) ( -64 506 64 0.750000 0.031250 ) ) +( ( -40 504 64 1.125000 0 ) ( -40.707031 504.707092 64 1.125000 0.015625 ) ( -41.414063 505.414185 64 1.125000 0.031250 ) ) +( ( -40 528 64 1.500000 0 ) ( -41 528 64 1.500000 0.015625 ) ( -42 528 64 1.500000 0.031250 ) ) +( ( -40 552 64 1.875000 0 ) ( -40.707031 551.292908 64 1.875000 0.015625 ) ( -41.414063 550.585815 64 1.875000 0.031250 ) ) +( ( -64 552 64 2.250000 0 ) ( -64 551 64 2.250000 0.015625 ) ( -64 550 64 2.250000 0.031250 ) ) +( ( -88 552 64 2.625000 0 ) ( -87.292969 551.292908 64 2.625000 0.015625 ) ( -86.585938 550.585815 64 2.625000 0.031250 ) ) +( ( -88 528 64 3 0 ) ( -87 528 64 3 0.015625 ) ( -86 528 64 3 0.031250 ) ) +) + } + } +} +// entity 10 +{ +"classname" "light" +"_color" "1.000000 0.501961 0.000000" +"scale" "4.5" +"light" "128" +"origin" "-64 528 96" +"style" "1" +} +// entity 11 +{ +"origin" "-64 528 76" +"soundSet" "small_fire " +"classname" "target_speaker" +} +// entity 12 +{ +"dmg" "15" +"classname" "trigger_hurt" +// brush 0 +{ +( -40 550 64 ) ( -86 550 64 ) ( -86 506 64 ) tools/_trigger 0 -128 0 0.125000 0.125000 0 0 0 +( -86 506 88 ) ( -86 550 88 ) ( -40 550 88 ) tools/_trigger 0 -128 0 0.125000 0.125000 0 0 0 +( -86 506 72 ) ( -40 506 72 ) ( -40 506 64 ) tools/_trigger 0 -32 0 0.125000 0.125000 0 0 0 +( -42 506 72 ) ( -42 550 72 ) ( -42 550 64 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +( -40 550 72 ) ( -86 550 72 ) ( -86 550 64 ) tools/_trigger 0 -32 0 0.125000 0.125000 0 0 0 +( -86 550 72 ) ( -86 506 72 ) ( -86 506 64 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +( -88 536 64 ) ( -72 552 64 ) ( -80 544 88 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +( -56 552 64 ) ( -40 536 64 ) ( -48 544 88 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +( -72 504 64 ) ( -88 520 64 ) ( -80 512 88 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +( -40 520 64 ) ( -56 504 64 ) ( -48 512 88 ) tools/_trigger 128 -32 0 0.125000 0.125000 0 0 0 +} +} +// entity 13 +{ +"type" "patchCapped" +"classname" "func_group" +// brush 0 + { + patchDef2 + { + kamchatka/coals + ( 3 3 0 0 0 ) +( +( ( -88 528 34 0 0 ) ( -88 552 34 0 0.375000 ) ( -64 552 34 0 0.750000 ) ) +( ( -88 504 34 0.375000 0 ) ( -64 528 34 0.375000 0.375000 ) ( -40 552 34 0.375000 0.750000 ) ) +( ( -64 504 34 0.750000 0 ) ( -40 504 34 0.750000 0.375000 ) ( -40 528 34 0.750000 0.750000 ) ) +) + } + } +} +// entity 14 +{ +"origin" "2240 512 120" +"message" "Test Room" +"classname" "target_location" +} +// entity 15 +{ +"target" "redflare" +"gametype" "ctf" +"targetname" "red_capture_point" +"classname" "gametype_trigger" +// brush 0 +{ +( -424 552 0 ) ( -472 552 0 ) ( -472 504 0 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -472 504 96 ) ( -472 552 96 ) ( -424 552 96 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -472 504 40 ) ( -424 504 40 ) ( -424 504 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -424 504 40 ) ( -424 552 40 ) ( -424 552 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -424 552 40 ) ( -472 552 40 ) ( -472 552 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -472 552 40 ) ( -472 504 40 ) ( -472 504 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +} +} +// entity 16 +{ +"targetname" "blue_flag" +"origin" "-832 528 24" +"gametype" "ctf" +"classname" "gametype_item" +} +// entity 17 +{ +"targetname" "red_flag" +"origin" "-448 528 24" +"gametype" "ctf" +"classname" "gametype_item" +} +// entity 18 +{ +"gametype" "ctf" +"targetname" "blue_capture_point" +"classname" "gametype_trigger" +// brush 0 +{ +( -808 552 0 ) ( -856 552 0 ) ( -856 504 0 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -856 504 96 ) ( -856 552 96 ) ( -808 552 96 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -856 504 40 ) ( -808 504 40 ) ( -808 504 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -808 504 40 ) ( -808 552 40 ) ( -808 552 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -808 552 40 ) ( -856 552 40 ) ( -856 552 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +( -856 552 40 ) ( -856 504 40 ) ( -856 504 8 ) tools/_trigger 0 0 0 0.125000 0.125000 0 0 0 +} +} +// entity 19 +{ +"delay" "300" +"effect" "flare_red.efx" +"targetname" "redflare" +"origin" "-448 680 16" +"classname" "target_effect" +} +// entity 20 +{ +"targetname" "briefcase" +"origin" "-1184 528 24" +"gametype" "inf" +"classname" "gametype_item" +} +// entity 21 +{ +"origin" "1768 528 40" +"model" "models/objects/common/trash_can_lid.md3" +"classname" "misc_model" +} +// entity 22 +{ +"origin" "1960 528 40" +"model" "models/objects/common/trash_can_lid.md3" +"classname" "model_static" +} diff --git a/bin/EffectsEd.exe b/bin/EffectsEd.exe new file mode 100644 index 0000000..57a72b4 Binary files /dev/null and b/bin/EffectsEd.exe differ diff --git a/bin/ModView.exe b/bin/ModView.exe new file mode 100644 index 0000000..31e9bad Binary files /dev/null and b/bin/ModView.exe differ diff --git a/bin/Radiant.exe b/bin/Radiant.exe new file mode 100644 index 0000000..d5ff89c Binary files /dev/null and b/bin/Radiant.exe differ diff --git a/bin/RadiantMP.bat b/bin/RadiantMP.bat new file mode 100644 index 0000000..e488560 --- /dev/null +++ b/bin/RadiantMP.bat @@ -0,0 +1 @@ +start radiant -noNPC -noConfusEd .\sof2mp.qe4 diff --git a/bin/ShaderEd2.exe b/bin/ShaderEd2.exe new file mode 100644 index 0000000..381f949 Binary files /dev/null and b/bin/ShaderEd2.exe differ diff --git a/bin/SoF2Asm.exe b/bin/SoF2Asm.exe new file mode 100644 index 0000000..23e8ce3 Binary files /dev/null and b/bin/SoF2Asm.exe differ diff --git a/bin/SoF2lcc.exe b/bin/SoF2lcc.exe new file mode 100644 index 0000000..3c41d1b Binary files /dev/null and b/bin/SoF2lcc.exe differ diff --git a/bin/brick.jpg b/bin/brick.jpg new file mode 100644 index 0000000..f6c38d7 Binary files /dev/null and b/bin/brick.jpg differ diff --git a/bin/carcass.exe b/bin/carcass.exe new file mode 100644 index 0000000..fa54903 Binary files /dev/null and b/bin/carcass.exe differ diff --git a/bin/clamp.jpg b/bin/clamp.jpg new file mode 100644 index 0000000..8beef82 Binary files /dev/null and b/bin/clamp.jpg differ diff --git a/bin/cpp.exe b/bin/cpp.exe new file mode 100644 index 0000000..533bfb1 Binary files /dev/null and b/bin/cpp.exe differ diff --git a/bin/dirt.jpg b/bin/dirt.jpg new file mode 100644 index 0000000..7b2da43 Binary files /dev/null and b/bin/dirt.jpg differ diff --git a/bin/entities.def b/bin/entities.def new file mode 100644 index 0000000..7c19d88 --- /dev/null +++ b/bin/entities.def @@ -0,0 +1,540 @@ +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + + +/*QUAKED pickup_armor_big (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_armor_medium (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_armor_small (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_health_big (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_health_small (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_weapon_US_SOCOM (0 .6 .6) (-15 -15 -15) (15 15 15) +Pistol, uses 45 rounds +*/ + + +/*QUAKED pickup_weapon_M19 (0 .6 .6) (-15 -15 -15) (15 15 15) +Pistol, uses 45 rounds +*/ + + +/*QUAKED pickup_weapon_microuzi (0 .6 .6) (-15 -15 -15) (15 15 15) +Sub-Machinegun, uses 9mm rounds +*/ + + +/*QUAKED pickup_weapon_M3A1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Sub-Machinegun, uses 45 rounds +*/ + + +/*QUAKED pickup_weapon_USAS_12 (0 .6 .6) (-15 -15 -15) (15 15 15) +Shotgun, uses 12-gauge rounds +ammo ---------- amount of ammo (defaults to 10) +*/ + + +/*QUAKED pickup_weapon_M590 (0 .6 .6) (-15 -15 -15) (15 15 15) +Shotgun, uses 12-gauge rounds +*/ + + +/*QUAKED pickup_weapon_MSG90A1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Sniper Rifle, uses 7.62 rounds +*/ + + +/*QUAKED pickup_weapon_M4 (0 .6 .6) (-15 -15 -15) (15 15 15) +Assault Rifle, uses 5.56 rounds and 40mm grenades +*/ + + +/*QUAKED pickup_weapon_AK_74 (0 .6 .6) (-15 -15 -15) (15 15 15) +Assault Rifle, uses 5.56 rounds +*/ + + +/*QUAKED pickup_weapon_M60 (0 .6 .6) (-15 -15 -15) (15 15 15) +Machinegun, uses 7.62 rounds +*/ + + +/*QUAKED pickup_weapon_RPG_7 (0 .6 .6) (-15 -15 -15) (15 15 15) +RPG, uses 40mm rounds +*/ + + +/*QUAKED pickup_weapon_MM_1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade Launcher, uses 40mm rounds +*/ + + +/*QUAKED pickup_weapon_M67 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_M84 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_F1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_L2A2 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_MDN11 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_SMOHG92 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + + +/*QUAKED pickup_weapon_AN_M14 (0 .6 .6) (-15 -15 -15) (15 15 15) +Incendiary Grenade +*/ + + +/*QUAKED pickup_weapon_M15 (0 .6 .6) (-15 -15 -15) (15 15 15) +White Phosphorus Grenade +*/ + + +/*QUAKED pickup_ammo_45 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_9mm (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_12gauge (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_762 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_556 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_40mm (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_ammo_rpg7 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED pickup_backpack (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -46) (16 16 48) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +*/ + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -46) (16 16 48) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ + + +/*QUAKED gametype_player (0 1 0) (-16 -16 -46) (16 16 48) REDTEAM BLUETEAM +Potential spawning position for red or blue team in custom gametype games. +*/ + + +/*QUAKED gametype_trigger (0 0 .8) ? +*/ + + +/*QUAKED gametype_item (0 0 1) (-16 -16 -16) (16 16 16) +"name" name of the item to spawn (defined in gametype script) +*/ + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. They are turned into normal brushes by the utilities. +*/ + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +target_position does the same thing +*/ + + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +Now that we don't have teleport destination pads, this is just +an info_notnull +*/ + + +/*QUAKED misc_model (1 0 0) (-16 -16 -16) (16 16 16) RMG +this model is inserted into the bsp file +"model" arbitrary .md3 file to display +*/ + + +/*QUAKED misc_G2model (1 0 0) (-16 -16 -16) (16 16 16) +"model" arbitrary .glm file to display +*/ + + +/*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) +The portal surface nearest this entity will show a view from the targeted misc_portal_camera, or a mirror view if untargeted. +This must be within 64 world units of the surface! +*/ + + +/*QUAKED misc_portal_camera (0 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate noswing +The target for a misc_portal_director. You can set either angles or target another entity to determine the direction of view. +"roll" an angle modifier to orient the camera around the target vector; +*/ + + +/*QUAKED misc_bsp (1 0 0) (-16 -16 -16) (16 16 16) +"bspmodel" arbitrary .bsp file to display +*/ + + +/*QUAKED terrain (1.0 1.0 1.0) ? +Terrain entity +It will stretch to the full height of the brush + +numPatches - integer number of patches to split the terrain brush into (default 200) +terxels - integer number of terxels on a patch side (default 4) (2 <= count <= 8) +seed - integer seed for random terrain generation (default 0) +textureScale - float scale of texture (default 0.005) +heightMap - name of heightmap data image to use +terrainDef - defines how the game textures the terrain (file is base/ext_data/*.terrain - default is grassyhills) +instanceDef - defines which bsp instances appear +miscentDef - defines which client models spawn on the terrain (file is base/ext_data/*.miscents) +densityMap - how dense the client models are packed + +*/ + + +/*QUAKED fx_play_effect (.2 .5 .8) (-8 -8 -8) (8 8 8) START_OFF +Plays specified effect file + +"effect" name of .efx file +"wait" seconds between triggerings, default 0.3 +"random" wait variance in seconds, default 0 +"target" direction of effect, default up +"count" plays effect this many times then deletes itself, default -1 = infinite + + +START_OFF fx starts off + + +*/ + + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"model2" .md3 model to also draw +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"health" if set, the door must be shot open +*/ + + +/*QUAKED func_plat (0 .5 .8) ? +Plats are always drawn in the extended position so they will light correctly. + +"lip" default 8, protrusion above rest position +"height" total height of movement, defaults to model height +"speed" overrides default 200. +"dmg" overrides default 2 +"model2" .md3 model to also draw +*/ + + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"model2" .md3 model to also draw +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +*/ + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) +Train path corners. +Target: next path corner and other targets to fire +"speed" speed to move to the next corner +"wait" seconds to wait before behining move to next corner +*/ + + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +A train is a mover that moves between path_corner target points. +Trains MUST HAVE AN ORIGIN BRUSH. +The train spawns at the first target it is pointing at. +"model2" .md3 model to also draw +"speed" default 100 +"dmg" default 2 +"noise" looping sound to play when the train is in motion +"target" next path corner +*/ + + +/*QUAKED func_static (0 .5 .8) ? +A bmodel that just sits there, doing nothing. Can be used for conditional walls and models. +"model2" .md3 model to also draw +*/ + + +/*QUAKED func_rotating (0 .5 .8) ? START_ON - X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"model2" .md3 model to also draw +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) +*/ + + +/*QUAKED func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS +Normally bobs on the Z axis +"model2" .md3 model to also draw +"height" amplitude of bob (32 default) +"speed" seconds to complete a bob cycle (4 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +*/ + + +/*QUAKED func_pendulum (0 .5 .8) ? +You need to have an origin brush as part of this entity. +Pendulums always swing north / south on unrotated models. Add an angles field to the model to allow rotation in other directions. +Pendulum frequency is a physical constant based on the length of the beam and gravity. +"model2" .md3 model to also draw +"speed" the number of degrees each way the pendulum swings, (30 default) +"phase" the 0.0 to 1.0 offset in the cycle to start at +"dmg" damage to inflict when blocked (2 default) +*/ + + +/*QUAKED func_glass (0 .5 .8) ? +Breakable glass +*/ + + +/*QUAKED worldspawn (0 0 0) ? + +Every map should have exactly one worldspawn. +"music" music wav file +"soundSet" soundset name to use (do not combine with 'noise', ignores all other flags) +"gravity" 800 is default gravity +"message" Text to print during connection process +"mission" Indicates which mission script file should be used to find the scripts for mission mode +*/ + + +/*QUAKED model_static (1 0 0) (-16 -16 -16) (16 16 16) NO_MP +"model" arbitrary .md3 file to display +*/ + + +/*QUAKED target_give (1 0 0) (-8 -8 -8) (8 8 8) +Gives the activator all the items pointed to. +*/ + + +/*QUAKED target_delay (1 0 0) (-8 -8 -8) (8 8 8) +"wait" seconds to pause before firing targets. +"random" delay variance, total delay = delay +/- random seconds +*/ + + +/*QUAKED target_score (1 0 0) (-8 -8 -8) (8 8 8) +"count" number of points to add, default 1 + +The activator is given this many points. +*/ + + +/*QUAKED target_print (1 0 0) (-8 -8 -8) (8 8 8) redteam blueteam private +"message" text to print +If "private", only the activator gets the message. If no checks, all clients get the message. +*/ + + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off global activator +"noise" wav file to play +"soundSet" soundset name to use (do not combine with 'noise', ignores all other flags) + +A global sound will play full volume throughout the level. +Activator sounds will play on the player that activated the target. +Global and activator sounds can't be combined with looping. +Normal sounds play each time the target is used. +Looped sounds will be toggled by use functions. +Multiple identical looping sounds will just increase volume without any speed cost. +"wait" : Seconds between auto triggerings, 0 = don't auto trigger +"random" wait variance, default is 0 +"radius" radius of attenuation +*/ + + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON +When triggered, fires a laser. You can either set a target or a direction. +*/ + + +/*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) +The activator will be teleported away. +*/ + + +/*QUAKED target_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) RED_ONLY BLUE_ONLY RANDOM +This doesn't perform any actions except fire its targets. +The activator can be forced to be from a certain team. +if RANDOM is checked, only one of the targets will be fired, not all of them +*/ + + +/*QUAKED target_kill (.5 .5 .5) (-8 -8 -8) (8 8 8) +Kills the activator. +*/ + + +/*QUAKED target_position (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for in-game calculation, like jumppad targets. +*/ + + +/*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) +Set "message" to the name of this location. + +Closest target_location in sight used for the location, if none +in site, closest in distance +*/ + + +/*QUAKED trigger_multiple (.5 .5 .5) ? +"wait" : Seconds between triggerings, 0.5 default, -1 = one time only. +"random" wait variance, default is 0 +Variable sized repeatable trigger. Must be targeted at one or more entities. +so, the basic time between firing is a random time between +(wait - random) and (wait + random) +*/ + + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ + + +/*QUAKED trigger_push (.5 .5 .5) ? +Must point at a target_position, which will be the apex of the leap. +This will be client side predicted, unlike target_push +*/ + + +/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) bouncepad +Pushes the activator in the direction.of angle, or towards a target apex. +"speed" defaults to 1000 +if "bouncepad", play bounce noise instead of windfly +*/ + + +/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR +Allows client side prediction of teleportation events. +Must point at a target_position, which will be the teleport destination. + +If spectator is set, only spectators can use this teleport +Spectator teleporters are not normally placed in the editor, but are created +automatically near doors to allow spectators to move through them +*/ + + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. +It does dmg points of damage each server frame +Targeting the trigger will toggle its on / off state. + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ + + +/*QUAKED trigger_ladder (.5 .5 .5) ? +Indicates a ladder and its normal + +"angles" angle ladder faces +*/ + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +This should be renamed trigger_timer... +Repeatedly fires its targets. +Can be turned on or off by using. + +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +*/ + + diff --git a/bin/none.jpg b/bin/none.jpg new file mode 100644 index 0000000..eb7997e Binary files /dev/null and b/bin/none.jpg differ diff --git a/bin/plugins/curry.dll b/bin/plugins/curry.dll new file mode 100644 index 0000000..708dfbe Binary files /dev/null and b/bin/plugins/curry.dll differ diff --git a/bin/plugins/gensurf.dll b/bin/plugins/gensurf.dll new file mode 100644 index 0000000..f4b2a8b Binary files /dev/null and b/bin/plugins/gensurf.dll differ diff --git a/bin/plugins/gensurf.ini b/bin/plugins/gensurf.ini new file mode 100644 index 0000000..fa6ddd7 --- /dev/null +++ b/bin/plugins/gensurf.ini @@ -0,0 +1,108 @@ +[Options] +Game=0 +Amplitude=128 +Roughness=16 +WaveLength=1024 +Extents=-512,-512,512,512 +CornerValues=0,0,0,0 +TextureOffset=0,0 +TextureScale=1,1 +NH=0x0008 +NV=0x0008 +AddHints=0x0000 +ArghRad2=0x0000 +AutoOverwrite=0x0000 +FixBorders=0x0001 +HideBackFaces=0x0000 +Plane=0x0000 +Preview=0x0000 +RandomSeed=0x0001 +Skybox=0x0000 +UseDetail=0x0000 +UseLadder=0x0000 +WaveType=0x0003 +vid_x=0x01c7 +vid_y=0x011c +view_x=0x0500 +view_y=0x0000 +view_cx=0x0867 +view_cy=0x0400 +UsePatches=0x0000 +SlantAngle=0x003c +[Quake2] +OutputDir= +Texture=e1u1/grass1_4 +Texture2= +Texture3= +TextureDir=c:\quake2\baseq2\textures\ +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Half-Life] +OutputDir= +Texture=OUT_GRND1 +Texture2= +Texture3= +TextureDir= +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[SiN] +OutputDir= +Texture=generic/floor_organic/fl_grass +Texture2= +Texture3= +TextureDir= +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Heretic2] +OutputDir= +Texture=canyon/canyon05 +Texture2= +Texture3= +TextureDir= +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Kingpin] +OutputDir= +Texture=bricks/s_sr_m3 +Texture2= +Texture3= +TextureDir=c:\kingpin\main\textures\ +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Genesis3D] +OutputDir= +Texture=rock13 +Texture2= +Texture3= +TextureDir= +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Quake3] +OutputDir= +Texture=organics/grass3 +Texture2=common/caulk +Texture3= +TextureDir= +UsePak=0x0000 +PakFile= +LastPakFile= +GameDir= +[Bitmap] +Filename=S:\design\gensurf_images\col2.bmp +DefaultPath=S:\design\gensurf_images\ +BlackValue=0 +WhiteValue=256 +[Formula] +Formula=256-radius/4+128*sin(radius/512) diff --git a/bin/plugins/max4/XSIImporter.dli b/bin/plugins/max4/XSIImporter.dli new file mode 100644 index 0000000..cba01a7 Binary files /dev/null and b/bin/plugins/max4/XSIImporter.dli differ diff --git a/bin/plugins/max4/vmdexp.dle b/bin/plugins/max4/vmdexp.dle new file mode 100644 index 0000000..2e35246 Binary files /dev/null and b/bin/plugins/max4/vmdexp.dle differ diff --git a/bin/plugins/purgeevil.dll b/bin/plugins/purgeevil.dll new file mode 100644 index 0000000..ba994e4 Binary files /dev/null and b/bin/plugins/purgeevil.dll differ diff --git a/bin/plugins/shapes.dll b/bin/plugins/shapes.dll new file mode 100644 index 0000000..9b520aa Binary files /dev/null and b/bin/plugins/shapes.dll differ diff --git a/bin/rcc.exe b/bin/rcc.exe new file mode 100644 index 0000000..1646e1c Binary files /dev/null and b/bin/rcc.exe differ diff --git a/bin/sof2data.exe b/bin/sof2data.exe new file mode 100644 index 0000000..86cb3dd Binary files /dev/null and b/bin/sof2data.exe differ diff --git a/bin/sof2map.exe b/bin/sof2map.exe new file mode 100644 index 0000000..0b5bb51 Binary files /dev/null and b/bin/sof2map.exe differ diff --git a/bin/sof2mp.qe4 b/bin/sof2mp.qe4 new file mode 100644 index 0000000..3efb9c8 --- /dev/null +++ b/bin/sof2mp.qe4 @@ -0,0 +1,23 @@ +{ +"basepath" "..\base" +"rshcmd" "" +"remotebasepath" "..\base" +"entitypath" "__QERPATH*.def" +"texturepath" "..\base\textures" +"autosave" "..\base\maps\autosave.map" + +"bsp FullVis (1/2 LMs)" "__QERPATHsof2map -bsp -rename -samplesize 32 $ -class 4 && __QERPATHsof2map -vis $ -class 2 && __QERPATHsof2map -light -extra -samplesize 32 $" +"bsp FullVis" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $ && __QERPATHsof2map -light $" +"bsp FullVis (extra)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $ && __QERPATHsof2map -light -extra $" +"bsp FullVis (nolight)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $" +"bsp FastVis (nolight)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis -fast $" +"bsp FastVis" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis -fast $ && __QERPATHsof2map -light $" +"bsp NoVis" "__QERPATHlocalbatch\novis.bat $" +"bsp OnlyEnts" "__QERPATHsof2map -bsp -rename -onlyents $" +"bsp Info" "__QERPATHsof2map -info $" +"bsp Relight (extra)" "__QERPATHsof2map -bsp -rename -onlyents $ && __QERPATHsof2map -light -extra $" +"bsp Relight (1/2 LM)" "__QERPATHsof2map -bsp -rename -onlyents $ && __QERPATHsof2map -light -extra -samplesize 32 $" +"brush_primit" "0" + +} + diff --git a/bin/sof2mp.qe4.duplicate1 b/bin/sof2mp.qe4.duplicate1 new file mode 100644 index 0000000..3efb9c8 --- /dev/null +++ b/bin/sof2mp.qe4.duplicate1 @@ -0,0 +1,23 @@ +{ +"basepath" "..\base" +"rshcmd" "" +"remotebasepath" "..\base" +"entitypath" "__QERPATH*.def" +"texturepath" "..\base\textures" +"autosave" "..\base\maps\autosave.map" + +"bsp FullVis (1/2 LMs)" "__QERPATHsof2map -bsp -rename -samplesize 32 $ -class 4 && __QERPATHsof2map -vis $ -class 2 && __QERPATHsof2map -light -extra -samplesize 32 $" +"bsp FullVis" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $ && __QERPATHsof2map -light $" +"bsp FullVis (extra)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $ && __QERPATHsof2map -light -extra $" +"bsp FullVis (nolight)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis $" +"bsp FastVis (nolight)" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis -fast $" +"bsp FastVis" "__QERPATHsof2map -bsp -rename $ && __QERPATHsof2map -vis -fast $ && __QERPATHsof2map -light $" +"bsp NoVis" "__QERPATHlocalbatch\novis.bat $" +"bsp OnlyEnts" "__QERPATHsof2map -bsp -rename -onlyents $" +"bsp Info" "__QERPATHsof2map -info $" +"bsp Relight (extra)" "__QERPATHsof2map -bsp -rename -onlyents $ && __QERPATHsof2map -light -extra $" +"bsp Relight (1/2 LM)" "__QERPATHsof2map -bsp -rename -onlyents $ && __QERPATHsof2map -light -extra -samplesize 32 $" +"brush_primit" "0" + +} + diff --git a/bin/stucco.jpg b/bin/stucco.jpg new file mode 100644 index 0000000..a1b3067 Binary files /dev/null and b/bin/stucco.jpg differ diff --git a/code/Sof2MP.dsp b/code/Sof2MP.dsp new file mode 100644 index 0000000..5a914bf --- /dev/null +++ b/code/Sof2MP.dsp @@ -0,0 +1,61 @@ +# Microsoft Developer Studio Project File - Name="Sof2MP" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Generic Project" 0x010a + +CFG=Sof2MP - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Sof2MP.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Sof2MP.mak" CFG="Sof2MP - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Sof2MP - Win32 Release" (based on "Win32 (x86) Generic Project") +!MESSAGE "Sof2MP - Win32 Debug" (based on "Win32 (x86) Generic Project") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +MTL=midl.exe + +!IF "$(CFG)" == "Sof2MP - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" + +!ELSEIF "$(CFG)" == "Sof2MP - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" + +!ENDIF + +# Begin Target + +# Name "Sof2MP - Win32 Release" +# Name "Sof2MP - Win32 Debug" +# End Target +# End Project diff --git a/code/Sof2MP.dsw b/code/Sof2MP.dsw new file mode 100644 index 0000000..a8a187e --- /dev/null +++ b/code/Sof2MP.dsw @@ -0,0 +1,149 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SoF2cgame"=.\cgame\sof2_cgame.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "SoF2game"=.\game\sof2_game.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "SoF2ui"=.\ui\sof2_ui.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "Sof2MP"=.\Sof2MP.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name SoF2cgame + End Project Dependency + Begin Project Dependency + Project_Dep_Name SoF2game + End Project Dependency + Begin Project Dependency + Project_Dep_Name SoF2ui + End Project Dependency + Begin Project Dependency + Project_Dep_Name gt_ctf + End Project Dependency + Begin Project Dependency + Project_Dep_Name gt_dm + End Project Dependency + Begin Project Dependency + Project_Dep_Name gt_elim + End Project Dependency + Begin Project Dependency + Project_Dep_Name gt_inf + End Project Dependency + Begin Project Dependency + Project_Dep_Name gt_tdm + End Project Dependency +}}} + +############################################################################### + +Project: "gt_ctf"=.\gametype\gt_ctf\gt_ctf.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gt_dm"=.\gametype\gt_dm\gt_dm.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gt_elim"=.\gametype\gt_elim\gt_elim.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gt_inf"=.\gametype\gt_inf\gt_inf.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "gt_tdm"=.\gametype\gt_tdm\gt_tdm.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/code/all.bat b/code/all.bat new file mode 100644 index 0000000..efdc9d2 --- /dev/null +++ b/code/all.bat @@ -0,0 +1,89 @@ +@set include= +@del /q ..\base\vm +@cd game +call game.bat +@cd ..\cgame +call cgame.bat +@cd ..\ui +call ui.bat +@cd ..\gametype\gt_ctf +call gt_ctf.bat +@cd ..\gt_inf +call gt_inf.bat +@cd ..\gt_elim +call gt_elim.bat +@cd ..\gt_dm +call gt_dm.bat +@cd ..\gt_tdm +call gt_tdm.bat +@cd ..\.. + +@echo off +echo . +echo . + +set bad = 0 +if not exist "game\vm\sof2mp_game.qvm" goto badGame +:testcgame +if not exist "cgame\vm\sof2mp_cgame.qvm" goto badCGame +:testui +if not exist "ui\vm\sof2mp_ui.qvm" goto badUI +:testdm +if not exist "gametype\gt_dm\vm\gt_dm.qvm" goto badDM +:testtdm +if not exist "gametype\gt_tdm\vm\gt_tdm.qvm" goto badTDM +:testctf +if not exist "gametype\gt_ctf\vm\gt_ctf.qvm" goto badCTF +:testinf +if not exist "gametype\gt_inf\vm\gt_inf.qvm" goto badINF +:testelim +if not exist "gametype\gt_elim\vm\gt_elim.qvm" goto badELIM +if %bad == "0" goto goodBuild +goto end + +:badGame +echo ***** SoF2MP_game.qvm did not build! +set bad = 1 +goto testcgame + +:badCGame +echo ***** SoF2MP_cgame.qvm did not build! +set bad = 1 +goto testui + +:badUI +echo ***** SoF2MP_ui.qvm did not build! +set bad = 1 +goto end + +:badDM +echo ***** gt_dm.qvm did not build! +set bad = 1 +goto end + +:badTDM +echo ***** gt_tdm.qvm did not build! +set bad = 1 +goto end + +:badCTF +echo ***** gt_ctf.qvm did not build! +set bad = 1 +goto end + +:badINF +echo ***** gt_inf.qvm did not build! +set bad = 1 +goto end + +:badELIM +echo ***** gt_elim.qvm did not build! +set bad = 1 +goto end + +:goodBuild +echo VMs were built successfully! + +:end +echo . +echo . diff --git a/code/cgame/animtable.h b/code/cgame/animtable.h new file mode 100644 index 0000000..a6e8b52 --- /dev/null +++ b/code/cgame/animtable.h @@ -0,0 +1,119 @@ +stringID_table_t bg_animTable [MAX_ANIMATIONS+1] = +{ + ENUM2STRING(BOTH_DEATH_NORMAL), + ENUM2STRING(BOTH_DEATH_NECK), + ENUM2STRING(BOTH_DEATH_CHEST_1), + ENUM2STRING(BOTH_DEATH_CHEST_2), + ENUM2STRING(BOTH_DEATH_GROIN_1), + ENUM2STRING(BOTH_DEATH_GROIN_2), + ENUM2STRING(BOTH_DEATH_GUT_1), + ENUM2STRING(BOTH_DEATH_GUT_2), + ENUM2STRING(BOTH_DEATH_HEAD_1), + ENUM2STRING(BOTH_DEATH_HEAD_2), + ENUM2STRING(BOTH_DEATH_SHOULDER_LEFT_1), + ENUM2STRING(BOTH_DEATH_SHOULDER_LEFT_2), + ENUM2STRING(BOTH_DEATH_ARMS_LEFT_1), + ENUM2STRING(BOTH_DEATH_ARMS_LEFT_2), + ENUM2STRING(BOTH_DEATH_LEGS_LEFT_1), + ENUM2STRING(BOTH_DEATH_LEGS_LEFT_2), + ENUM2STRING(BOTH_DEATH_LEGS_LEFT_3), + ENUM2STRING(BOTH_DEATH_THIGH_LEFT_1), + ENUM2STRING(BOTH_DEATH_THIGH_LEFT_2), + ENUM2STRING(BOTH_DEATH_ARMS_RIGHT_1), + ENUM2STRING(BOTH_DEATH_ARMS_RIGHT_2), + ENUM2STRING(BOTH_DEATH_LEGS_RIGHT_1), + ENUM2STRING(BOTH_DEATH_LEGS_RIGHT_2), + ENUM2STRING(BOTH_DEATH_LEGS_RIGHT_3), + ENUM2STRING(BOTH_DEATH_SHOULDER_RIGHT_1), + ENUM2STRING(BOTH_DEATH_SHOULDER_RIGHT_2), + ENUM2STRING(BOTH_DEATH_THIGH_RIGHT_1), + ENUM2STRING(BOTH_DEATH_THIGH_RIGHT_2), + + ENUM2STRING(TORSO_DROP), + ENUM2STRING(TORSO_DROP_ONEHANDED), + ENUM2STRING(TORSO_DROP_KNIFE), + ENUM2STRING(TORSO_RAISE), + ENUM2STRING(TORSO_RAISE_ONEHANDED), + ENUM2STRING(TORSO_RAISE_KNIFE), + + ENUM2STRING(LEGS_IDLE), + ENUM2STRING(LEGS_IDLE_CROUCH), + ENUM2STRING(LEGS_WALK), + ENUM2STRING(LEGS_WALK_BACK), + ENUM2STRING(LEGS_WALK_CROUCH), + ENUM2STRING(LEGS_WALK_CROUCH_BACK), + + ENUM2STRING(LEGS_RUN), + ENUM2STRING(LEGS_RUN_BACK), + + ENUM2STRING(LEGS_SWIM), + + ENUM2STRING(LEGS_JUMP), + ENUM2STRING(LEGS_JUMP_BACK), + + ENUM2STRING(LEGS_TURN), + + ENUM2STRING(LEGS_LEAN_LEFT), + ENUM2STRING(LEGS_LEAN_RIGHT), + ENUM2STRING(LEGS_LEAN_CROUCH_LEFT), + ENUM2STRING(LEGS_LEAN_CROUCH_RIGHT), + + ENUM2STRING(LEGS_LEANLEFT_WALKLEFT), + ENUM2STRING(LEGS_LEANLEFT_WALKRIGHT), + ENUM2STRING(LEGS_LEANRIGHT_WALKLEFT), + ENUM2STRING(LEGS_LEANRIGHT_WALKRIGHT), + + ENUM2STRING(LEGS_LEANLEFT_CROUCH_WALKLEFT), + ENUM2STRING(LEGS_LEANLEFT_CROUCH_WALKRIGHT), + ENUM2STRING(LEGS_LEANRIGHT_CROUCH_WALKLEFT), + ENUM2STRING(LEGS_LEANRIGHT_CROUCH_WALKRIGHT), + + ENUM2STRING(TORSO_IDLE_KNIFE ), + ENUM2STRING(TORSO_IDLE_PISTOL ), + ENUM2STRING(TORSO_IDLE_RIFLE ), + ENUM2STRING(TORSO_IDLE_MSG90A1_ZOOMED ), + ENUM2STRING(TORSO_IDLE_M4 ), + ENUM2STRING(TORSO_IDLE_M590 ), + ENUM2STRING(TORSO_IDLE_USAS12 ), + ENUM2STRING(TORSO_IDLE_RPG), + ENUM2STRING(TORSO_IDLE_M60), + ENUM2STRING(TORSO_IDLE_MM1), + ENUM2STRING(TORSO_IDLE_GRENADE), + + ENUM2STRING(TORSO_ATTACK_KNIFE ), + ENUM2STRING(TORSO_ATTACK_KNIFE_THROW ), + ENUM2STRING(TORSO_ATTACK_PISTOL ), + ENUM2STRING(TORSO_ATTACK_RIFLE ), + ENUM2STRING(TORSO_ATTACK_MSG90A1_ZOOMED ), + ENUM2STRING(TORSO_ATTACK_M4 ), + ENUM2STRING(TORSO_ATTACK_M590 ), + ENUM2STRING(TORSO_ATTACK_USAS12 ), + ENUM2STRING(TORSO_ATTACK_RIFLEBUTT ), + ENUM2STRING(TORSO_ATTACK_RPG), + ENUM2STRING(TORSO_ATTACK_M60), + ENUM2STRING(TORSO_ATTACK_MM1), + ENUM2STRING(TORSO_ATTACK_GRENADE_START ), + ENUM2STRING(TORSO_ATTACK_GRENADE_END ), + ENUM2STRING(TORSO_ATTACK_BAYONET ), + ENUM2STRING(TORSO_ATTACK_PISTOLWHIP ), + + ENUM2STRING(TORSO_RELOAD_M60), + ENUM2STRING(TORSO_RELOAD_PISTOL), + ENUM2STRING(TORSO_RELOAD_RIFLE), + ENUM2STRING(TORSO_RELOAD_MSG90A1), + ENUM2STRING(TORSO_RELOAD_RPG), + ENUM2STRING(TORSO_RELOAD_USAS12), + + ENUM2STRING(TORSO_RELOAD_M590_START), + ENUM2STRING(TORSO_RELOAD_M590_SHELL), + ENUM2STRING(TORSO_RELOAD_M590_END), + + ENUM2STRING(TORSO_RELOAD_MM1_START), + ENUM2STRING(TORSO_RELOAD_MM1_SHELL), + ENUM2STRING(TORSO_RELOAD_MM1_END), + + //must be terminated + NULL,-1 +}; + + diff --git a/code/cgame/cg_consolecmds.c b/code/cgame/cg_consolecmds.c new file mode 100644 index 0000000..e46ecaa --- /dev/null +++ b/code/cgame/cg_consolecmds.c @@ -0,0 +1,576 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" +#include "../ui/ui_shared.h" +#include "../ui/ui_public.h" + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f (void) +{ + Com_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdef.viewangles[YAW]); +} + +/* +============= +CG_ScoresDown_f +============= +*/ +static void CG_ScoresDown_f( void ) +{ + if ( cg.scoresRequestTime + 2000 < cg.time ) + { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) + { + cg.showScores = qtrue; + cg.numScores = 0; + } + } + else + { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +/* +============= +CG_ScoresUp_f +============= +*/ +static void CG_ScoresUp_f( void ) +{ + if ( cg.showScores ) + { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + +/* +============= +CG_AutomapDown_f +============= +*/ +static void CG_AutomapDown_f( void ) +{ + cg.showAutomap = qtrue; +} + +/* +============= +CG_AutomapUp_f +============= +*/ +static void CG_AutomapUp_f( void ) +{ + cg.showAutomap = qfalse; +} + +/* +============= +CG_ReloadHud_f +============= +*/ +static void CG_ReloadHud_f ( void ) +{ + // Reset the string table used for menus + String_Init(); + + // Clear all menus + Menu_Reset(); + + // Reload the menus + CG_LoadMenus ( "ui/hud.txt" ); +} + +/* +============= +CG_Radio_f + +Bring up the radio menu if all the conditions are met +============= +*/ +static void CG_Radio_f ( void ) +{ + // Only in team games + if ( !cgs.gametypeData->teams ) + { + return; + } + + // Not when ghosting or following + if ( cg.snap->ps.pm_flags & (PMF_FOLLOW|PMF_GHOST) ) + { + return; + } + + // Not when a spectator + if ( cg.snap->ps.pm_type == PM_SPECTATOR ) + { + return; + } + + trap_UI_SetActiveMenu ( UIMENU_RADIO ); +} + +/* +============= +CG_Objectives_f + +Bring up the objectives menu if all the conditions are met +============= +*/ +static void CG_Objectives_f ( void ) +{ + // Dont bother popping up the objectives dialog if there is + // no objective text + if ( !cgs.gametypeData->description ) + { + return; + } + + trap_UI_SetActiveMenu ( UIMENU_OBJECTIVES ); +} + +/* +============= +CG_Outfitting_f + +Bring up the outfitting menu if all the conditions are met +============= +*/ +static void CG_Outfitting_f ( void ) +{ + // Only allow outfitting when pickups are disabled + if ( !cgs.pickupsDisabled ) + { + return; + } + + trap_UI_SetActiveMenu ( UIMENU_OUTFITTING ); +} + +/* +============= +CG_Team_f + +bring up the team selection user interface +============= +*/ +static void CG_Team_f ( void ) +{ + // No team menu in non-team games + if ( !cgs.gametypeData->teams ) + { + return; + } + + // Special case which only brings up the team menu if its the clients first + // time to the objectives dialog + if ( atoi ( CG_Argv(1) ) ) + { + if ( ui_info_seenobjectives.integer ) + { + return; + } + } + + CG_UpdateTeamCountCvars ( ); + + trap_Cvar_Set ( "ui_info_seenobjectives", "1" ); + trap_UI_SetActiveMenu ( UIMENU_TEAM ); +} + +/* +============= +CG_Drop_f + +Drops the selected weapon +============= +*/ +void CG_Drop_f ( void ) +{ + char cmd[128]; + int exclude; + + // Cant drop when following or a ghost + if ( cg.snap->ps.pm_flags & (PMF_FOLLOW|PMF_GHOST) ) + { + return; + } + + // Either dead or spectating if not normal + if ( cg.predictedPlayerState.pm_type != PM_NORMAL ) + { + return; + } + + // Can only drop a weapon when in ready state + if ( cg.predictedPlayerState.weaponstate != WEAPON_READY ) + { + return; + } + + // Cant drop the knife + if( cg.weaponSelect == WP_KNIFE ) + { + return; + } + + // Close the menu since a weapon is being dropped + cg.weaponMenuUp = qfalse; + + // Build the server command + Com_sprintf( cmd, 128, "drop %i", cg.weaponSelect ); + + // Go to next weapon before the current drops + exclude = cg.weaponSelect; + cg.weaponSelect = WP_M67_GRENADE; + CG_PrevWeapon ( qfalse, exclude ); + + // Send server comand + trap_SendClientCommand( cmd ); +} + +/* +============= +CG_GetOutfittingGroupFromString + +Converts the given string into an outfitting group id +============= +*/ +static int CG_GetOutfittingGroupFromString ( const char* str ) +{ + if ( Q_stricmp ( str, "primary" ) == 0 ) + { + return OUTFITTING_GROUP_PRIMARY; + } + else if ( Q_stricmp ( str, "secondary" ) == 0 ) + { + return OUTFITTING_GROUP_SECONDARY; + } + else if ( Q_stricmp ( str, "pistol" ) == 0 ) + { + return OUTFITTING_GROUP_PISTOL; + } + else if ( Q_stricmp ( str, "grenade" ) == 0 ) + { + return OUTFITTING_GROUP_GRENADE; + } + else if ( Q_stricmp ( str, "knife" ) == 0 ) + { + return OUTFITTING_GROUP_KNIFE; + } + + return -1; +} + +/* +============= +CG_WeaponToggle_f + +toggles between multiple weapons +============= +*/ +static void CG_WeaponToggle_f ( void ) +{ + int group1; + int group2; + int weapon1; + int weapon2; + gitem_t* item; + int i; + + // Get the toggle groups + group1 = CG_GetOutfittingGroupFromString ( CG_Argv(1) ); + group2 = CG_GetOutfittingGroupFromString ( CG_Argv(2) ); + + // Invalid toggle if either is -1 + if ( group1 == -1 ) // || group2 == -1 ) + { + return; + } + + // Make sure they have something from both of the groups + weapon1 = WP_NONE; + weapon2 = WP_NONE; + for ( i = WP_KNIFE; i < WP_NUM_WEAPONS; i ++ ) + { + // Make sure this weapon is selectable. + if ( !CG_WeaponSelectable ( i, qtrue ) ) + { + continue; + } + + item = BG_FindWeaponItem ( i ); + if ( item->outfittingGroup == group1 ) + { + weapon1 = i; + } + else if ( item->outfittingGroup == group2 ) + { + weapon2 = i; + } + } + + // IF only one of the two weapons is available then go to it + if ( weapon1 == WP_NONE && weapon2 == WP_NONE ) + { + return; + } + else if ( weapon1 == WP_NONE ) + { + cg.weaponSelect = weapon2; + return; + } + else if ( weapon2 == WP_NONE ) + { + cg.weaponSelect = weapon1; + return; + } + + // They have both weapons, so figure out which to go to + item = BG_FindWeaponItem ( cg.weaponSelect ); + + if ( item->outfittingGroup == group1 ) + { + cg.weaponSelect = weapon2; + } + else + { + cg.weaponSelect = weapon1; + } +} + +/* +============= +CG_NextWeapon_f + +selects next weapon in inventory and allows empty weapons to +be selected +============= +*/ +static void CG_NextWeapon_f ( void ) +{ + CG_NextWeapon ( qtrue, -1 ); +} + +/* +============= +CG_PrevWeapon_f + +selects previous weapon in inventory and allows empty weapons +to be selectd +============= +*/ +static void CG_PrevWeapon_f ( void ) +{ + CG_PrevWeapon ( qtrue, -1 ); +} + +/* +============= +CG_LastWeapon_f + +Selects the last weapon that was selected +============= +*/ +static void CG_LastWeapon_f ( void ) +{ + if ( CG_WeaponSelectable ( cg.weaponLastSelect, qtrue ) ) + { + int swap = cg.weaponSelect; + cg.weaponSelect = cg.weaponLastSelect; + cg.weaponLastSelect = swap; + } +} + + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +/* +================== +CG_StartOrbit_f +================== +*/ + +static void CG_StartOrbit_f( void ) { + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); + if ( !atoi(var) ) { + return; + } + if (cg_cameraOrbit.value != 0) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + } else { + trap_Cvar_Set("cg_cameraOrbit", "5"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + } +} + +typedef struct +{ + char *cmd; + void (*function)(void); + +} consoleCommand_t; + +static consoleCommand_t commands[] = +{ + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "+automap", CG_AutomapDown_f }, + { "-automap", CG_AutomapUp_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weaplast", CG_LastWeapon_f }, + { "weapon", CG_Weapon_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "reloadhud", CG_ReloadHud_f }, + { "startOrbit", CG_StartOrbit_f }, + { "loaddeferred", CG_LoadDeferredPlayers }, + { "drop", CG_Drop_f }, + + { "weaptoggle", CG_WeaponToggle_f }, + + { "ui_radio", CG_Radio_f }, + { "ui_objectives", CG_Objectives_f }, + { "ui_outfitting", CG_Outfitting_f }, + { "ui_team", CG_Team_f }, +}; + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) +{ + const char *cmd; + int i; + + // No console commands when a map is changing + if ( cg.mMapChange ) + { + return qfalse; + } + + cmd = CG_Argv(0); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) + { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) + { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) +{ + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) + { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + trap_AddCommand ("vsay_team"); + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); +#ifdef _SOF2_BOTS + trap_AddCommand ("addbot"); +#endif + trap_AddCommand ("setviewpos"); + trap_AddCommand ("callvote"); + trap_AddCommand ("vote"); + trap_AddCommand ("stats"); + trap_AddCommand ("teamtask"); + trap_AddCommand ("loaddeferred"); + trap_AddCommand ("gametype_restart"); +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c new file mode 100644 index 0000000..527cd0f --- /dev/null +++ b/code/cgame/cg_draw.c @@ -0,0 +1,1842 @@ +// Copyright (C) 2001-2002 Raven Software +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" + +#include "../ui/ui_shared.h" + +// used for scoreboard +extern displayContextDef_t cgDC; + +void CG_DrawRadar ( void ); +void CG_DrawAutomap ( void ); +void CG_DrawTimers ( void ); + +/* +================ +CG_Draw3DModel +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) +{ + refdef_t refdef; + refEntity_t ent; + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +void CG_Draw3DG2Model( float x, float y, float w, float h, void *ghoul2, qhandle_t skin, vec3_t origin, vec3_t angles ) +{ + refdef_t refdef; + refEntity_t ent; + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.ghoul2 = ghoul2; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) +{ + char *s; + int w; + + s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ); + + CG_DrawText ( 635 - w, y - 6, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + return 20; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) +{ + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + + if ( index > FPS_FRAMES ) + { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%i fps", fps ); + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ); + + CG_DrawText ( 635 - w, y, cgs.media.hudFont, 0.53f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0,0 ); + } + + return y + 20; +} + +/* +===================== +CG_DrawRadar +===================== +*/ +#define RADAR_RANGE 2500 +#define RADAR_RADIUS 60 +#define RADAR_X (580 - RADAR_RADIUS) +#define RADAR_Y 10 +#define RADAR_CHAT_DURATION 6000 + +void CG_DrawRadar ( void ) +{ + vec4_t color; + vec4_t teamColor; + float arrow_w; + float arrow_h; + clientInfo_t *cl; + clientInfo_t *local; + int i; + float scale; + + // Make sure the radar should be showing + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 || cg.weaponMenuUp ) + { + return; + } + + if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) || cgs.clientinfo[cg.predictedPlayerState.clientNum].team == TEAM_SPECTATOR ) + { + return; + } + + local = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + if ( !local->infoValid ) + { + return; + } + + // Draw the radar background image + color[0] = color[1] = color[2] = 1.0f; + color[3] = 0.6f; + trap_R_SetColor ( color ); + CG_DrawPic( RADAR_X, RADAR_Y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader ); + + arrow_w = 16 * RADAR_RADIUS / 128; + arrow_h = 16 * RADAR_RADIUS / 128; + + // determine the color of the arrows to draw + switch(local->team) + { + default: + case TEAM_RED: + VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], teamColor ); + break; + + case TEAM_BLUE: + VectorCopy ( g_color_table[ColorIndex(COLOR_BLUE)], teamColor ); + break; + } + + // Darken the color a tad + VectorScale ( teamColor, 0.5f, teamColor ); + teamColor[3] = 1.0f; + + // Draw all of the radar entities. Draw them backwards so players are drawn last + for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- ) + { + vec3_t dirLook; + vec3_t dirPlayer; + float angleLook; + float anglePlayer; + float angle; + float distance; + centity_t* cent; + + cent = cg.radarEntities[i]; + + // Get the distances first + VectorSubtract ( cg.predictedPlayerState.origin, cent->lerpOrigin, dirPlayer ); + dirPlayer[2] = 0; + distance = VectorNormalize ( dirPlayer ); + + if ( distance > RADAR_RANGE * 0.8f) + { + continue; + } + + distance = distance / RADAR_RANGE; + distance *= RADAR_RADIUS; + + AngleVectors ( cg.predictedPlayerState.viewangles, dirLook, NULL, NULL ); + + dirLook[2] = 0; + anglePlayer = atan2(dirPlayer[0],dirPlayer[1]); + VectorNormalize ( dirLook ); + angleLook = atan2(dirLook[0],dirLook[1]); + angle = angleLook - anglePlayer; + + switch ( cent->currentState.eType ) + { + case ET_ITEM: + if ( cg_items[cent->currentState.modelindex].registered ) + { + float x; + float y; + + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + y = (float)RADAR_Y + (float)RADAR_RADIUS + (float)cos (angle) * distance; + + trap_R_SetColor ( NULL ); + CG_DrawPic ( x - 4, y - 4, 9, 9, cg_items[cent->currentState.modelindex].icon ); + } + break; + + case ET_PLAYER: + cl = &cgs.clientinfo[ cent->currentState.number ]; + + // not valid then dont draw it + if ( !cl->infoValid ) + { + continue; + } + + if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time) + { + vec3_t finalColor; + + scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION); + scale *= scale; + + finalColor[0] = (teamColor[0] * (scale)) + (colorWhite[0] * (1.0-scale)); + finalColor[1] = (teamColor[1] * (scale)) + (colorWhite[1] * (1.0-scale)); + finalColor[2] = (teamColor[2] * (scale)) + (colorWhite[2] * (1.0-scale)); + finalColor[3] = teamColor[3]; + trap_R_SetColor ( finalColor ); + scale += 1.0; + } + else + { + trap_R_SetColor ( teamColor ); + scale = 1.0; + } + + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance, + RADAR_Y + RADAR_RADIUS + cos (angle) * distance, + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg.predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); + break; + } + } + + trap_R_SetColor ( colorWhite ); + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS, RADAR_Y + RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); +} + +/* +===================== +CG_DrawTeamScores +===================== +*/ +static void CG_DrawTeamScores ( float y ) +{ + char scores[2][16]; + float w; + const char* s; + + if ( cgs.scores1 == SCORE_NOT_PRESENT ) + { + Com_sprintf (scores[0], sizeof(scores[0]), "-"); + } + else + { + Com_sprintf (scores[0], sizeof(scores[0]), "%i", cgs.scores1); + } + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) + { + Com_sprintf (scores[1], sizeof(scores[1]), "-"); + } + else + { + Com_sprintf (scores[1], sizeof(scores[1]), "%i", cgs.scores2); + } + + s = va ( "Red: %s Blue: %s", scores[0], scores[1] ); + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.35f, 0 ); + + CG_DrawText ( RADAR_X + RADAR_RADIUS - w / 2, y, cgs.media.hudFont, 0.35f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0, DT_OUTLINE ); +} + +/* +===================== +CG_DrawUpperRight +===================== +*/ +static void CG_DrawUpperRight( void ) +{ + float y; + + y = 0; + + if ( cg.scoreBoardShowing ) + { + return; + } + + if ( cg_drawSnapshot.integer ) + { + y = CG_DrawSnapshot( y ); + } + + switch ( cg_drawRadar.integer ) + { + // Off unless the key is pressed + case 0: + if ( cg.showAutomap ) + { + // If in rmg default to the auto map and if in a non team game + // the radar is useless so default to automap as well. + if ( cg.mInRMG || !cgs.gametypeData->teams ) + { + CG_DrawAutomap ( ); + } + else + { + CG_DrawRadar ( ); + } + } + break; + + // Draw the radar unless the automap key is down + case 1: + if ( cg.showAutomap && cg.mInRMG ) + { + CG_DrawAutomap ( ); + } + // If its a team game allow radar + else if ( cgs.gametypeData->teams ) + { + CG_DrawRadar ( ); + } + break; + + // Draw the automap only, but if the key is pressed show the radar + case 2: + if ( cgs.gametypeData->teams && (cg.showAutomap || !cg.mInRMG) ) + { + CG_DrawRadar ( ); + } + else + { + CG_DrawAutomap ( ); + } + break; + } + + if ( cg_drawFPS.integer ) + { + y = CG_DrawFPS( y ); + y = 18; + } + else + { + y = 10; + } + + if ( cg_drawTeamScores.integer && cgs.gametypeData->teams ) + { + CG_DrawTeamScores ( y ); + } + + CG_DrawTimers ( ); +} + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) +{ + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) +{ + // dropped packet + if ( !snap ) + { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Draws a little connection icon on the screen when connection to the +server is lost +============== +*/ +static void CG_DrawDisconnect ( void ) +{ + float x; + float y; + int cmdNum; + usercmd_t cmd; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + + // special check for map_restart + if ( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) + { + return; + } + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) + { + return; + } + + x = 640 - 76; + y = 480 - 165; + + CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.disconnectShader ); +} + + +static vec4_t ChatColor = +{ // green + 0.0, 1.0, 0.0, 1.0 +}; + +/* +============== +CG_DrawAutomap + +Draws the rmg map with any radar items placed on it. +============== +*/ +void CG_DrawAutomap ( void ) +{ + TCGConvertPos *pos = (TCGConvertPos *)cg.sharedBuffer; + clientInfo_t *cl, *local; + int i; + qhandle_t teamIcon; + vec4_t teamColor, finalColor; + int arrow_w; + int arrow_h; + int item_w; + int item_h; + float scale; + + // Only enabled in the RMG + if (!cg.mInRMG || cg.weaponMenuUp ) + { + return; + } + + // invalid parms, so don't draw! + if (cg_automap_x.integer < 0 || cg_automap_x.integer > 640-32 || + cg_automap_y.integer < 0 || cg_automap_y.integer > 480-32 || + cg_automap_w.integer < 32 || cg_automap_w.integer > 640 || + cg_automap_h.integer < 32 || cg_automap_h.integer > 480 || + cg_automap_x.integer + cg_automap_w.integer > 640 || + cg_automap_y.integer + cg_automap_h.integer > 480) + { + Com_Printf("Automap drawing coordinates out of range!\n"); + return; + } + + // Register the automap image if not already registerd + if ( !cgs.media.mAutomap ) + { + trap_CM_TM_Upload(0, 0); + cgs.media.mAutomap = trap_R_RegisterShader( "gfx/menus/rmg/automap" ); + } + + finalColor[0] = finalColor[1] = finalColor[2] = 1.0; + finalColor[3] = cg_automap_a.value; + if (finalColor[3] > 1.0) + { + finalColor[3] = 1.0; + } + + if ( cgs.media.mAutomap) + { + trap_R_SetColor (finalColor); + CG_DrawPic( cg_automap_x.integer, cg_automap_y.integer, cg_automap_w.integer, cg_automap_h.integer, cgs.media.mAutomap ); + } + + local = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + if ( !local->infoValid) + { + return; + } + + arrow_w = 16 * cg_automap_w.integer / 512; + arrow_h = 16 * cg_automap_h.integer / 512; + item_w = 24 * cg_automap_w.integer / 512; + item_h = 24 * cg_automap_h.integer / 512; + + switch(local->team) + { + case TEAM_RED: + teamColor[0] = 1.0; + teamColor[1] = 0.0; + teamColor[2] = 0.0; + break; + case TEAM_BLUE: + teamColor[0] = 0.0; + teamColor[1] = 0.0; + teamColor[2] = 1.0; + break; + case TEAM_SPECTATOR: + default: + teamColor[0] = 1.0; + teamColor[1] = 1.0; + teamColor[2] = 1.0; + break; + } + teamIcon = cgs.media.mAutomapPlayerIcon; + teamColor[3] = cg_automap_a.value + 0.1; + if (teamColor[3] > 1.0) + { + teamColor[3] = 1.0; + } + + // Only team games show other people on the automap + if ( cgs.gametypeData->teams ) + { + // Draw all of the radar entities. Draw them backwards so players are drawn last + for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- ) + { + centity_t* cent; + + cent = cg.radarEntities[i]; + + switch ( cent->currentState.eType ) + { + case ET_ITEM: + if ( cg_items[cent->currentState.modelindex].registered ) + { + VectorCopy( cent->lerpOrigin, pos->mOrigin); + pos->mWidth = cg_automap_w.integer; + pos->mHeight = cg_automap_h.integer; + trap_CM_TM_ConvertPosition(); + + trap_R_SetColor ( NULL ); + CG_DrawPic ( pos->mX - item_w / 2 + cg_automap_x.integer, + pos->mY - item_h / 2 + cg_automap_y.integer, + item_w, item_h, cg_items[cent->currentState.modelindex].icon ); + } + break; + + case ET_PLAYER: + + cl = &cgs.clientinfo[ cent->currentState.number ]; + + // not valid then dont draw it + if ( !cl->infoValid ) + { + continue; + } + + if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time) + { + vec3_t finalColor; + + scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION); + scale *= scale; + + finalColor[0] = (teamColor[0] * (scale)) + (colorWhite[0] * (1.0-scale)); + finalColor[1] = (teamColor[1] * (scale)) + (colorWhite[1] * (1.0-scale)); + finalColor[2] = (teamColor[2] * (scale)) + (colorWhite[2] * (1.0-scale)); + finalColor[3] = teamColor[3]; + trap_R_SetColor ( finalColor ); + scale += 1.0; + } + else + { + trap_R_SetColor ( teamColor ); + scale = 1.0; + } + + VectorCopy( cent->lerpOrigin, pos->mOrigin); + pos->mWidth = cg_automap_w.integer; + pos->mHeight = cg_automap_h.integer; + trap_CM_TM_ConvertPosition(); + + CG_DrawRotatePic2( pos->mX + cg_automap_x.integer, + pos->mY + cg_automap_y.integer, + arrow_w*scale, arrow_h*scale, + (360 - cent->lerpAngles[1]) - 90.0, teamIcon ); + break; + } + } + } + + VectorCopy(cg.refdef.vieworg, pos->mOrigin); + pos->mWidth = cg_automap_w.integer; + pos->mHeight = cg_automap_h.integer; + trap_CM_TM_ConvertPosition(); + + teamColor[0] = teamColor[1] = teamColor[2] = 1.0; + teamColor[3] = cg_automap_a.value + 0.2; + if (teamColor[3] > 1.0) + { + teamColor[3] = 1.0; + } + trap_R_SetColor ( teamColor ); + scale = 1.0; + + CG_DrawRotatePic2( pos->mX + cg_automap_x.integer, pos->mY + cg_automap_y.integer, arrow_w*scale, arrow_h*scale, + (360 - cg.refdef.viewangles[1]) - 90.0, cgs.media.mAutomapPlayerIcon ); +} + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) +{ + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + trap_R_SetColor( NULL ); + + if ( !cg_lagometer.integer || cgs.localServer ) + { + CG_DrawDisconnect(); + return; + } + + // draw the graph + x = 640 - 76; + y = 480 - 165; + + CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + CG_DrawDisconnect(); +} + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, float scale ) +{ + char *s; + + Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintScale = scale; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) + { + if (*s == '\n') + { + cg.centerPrintLines++; + } + + s++; + } +} + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) +{ + char *start; + int l; + int x, y, w; + int h; + float *color; + + if ( !cg_centertime.value ) + { + return; + } + + if ( !cg.centerPrintTime ) + { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) + { + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg_centerY.integer; + + while ( 1 ) + { + char linebuffer[1024]; + + for ( l = 0; l < 50; l++ ) + { + if ( !start[l] || start[l] == '\n' ) + { + break; + } + + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + + w = trap_R_GetTextWidth(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 ); + h = trap_R_GetTextHeight(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 ); + x = (SCREEN_WIDTH - w) / 2; + CG_DrawText (x, y + h, cgs.media.hudFont, cg.centerPrintScale, color, linebuffer, 0, DT_OUTLINE ); + y += h + 6; + + while ( *start && ( *start != '\n' ) ) + { + start++; + } + + if ( !*start ) + { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + +/* +================= +CG_DrawCenterText +================= +*/ +static void CG_DrawCenterText ( void ) +{ + int w; + + if ( cgs.gametypeMessageTime < cg.time ) + { + CG_DrawCenterString ( ); + return; + } + + w = trap_R_GetTextWidth( cgs.gametypeMessage, cgs.media.hudFont, 0.43f, 0 ); + CG_DrawText ( (SCREEN_WIDTH - w) / 2, cg_centerY.integer, cgs.media.hudFont, 0.43f, colorWhite, cgs.gametypeMessage, 0, DT_OUTLINE ); +} + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair(void) +{ + float w; + float h; + qhandle_t hShader; + float x; + float y; + float scale; + int ca; + qboolean zoomed; + + if ( !cg_drawCrosshair.integer ) + { + return; + } + + // If zoomed or unzoomed with the sniper rifle dont draw the standard crosshair + zoomed = (cg.predictedPlayerState.pm_flags&PMF_ZOOMED); + if ( zoomed || (cg.predictedPlayerState.weapon==WP_MSG90A1 && !zoomed) ) + { + return; + } + + if ( cg.snap->ps.pm_type == PM_SPECTATOR ) + { + return; + } + + if ( cg.renderingThirdPerson ) + { + return; + } + + // Default crosshair color + trap_R_SetColor( cg.crosshairRGBA ); + + // Change the crosshair color when its on friendly targets + if ( cg.crosshairColorClientNum != -1 && cgs.gametypeData->teams ) + { + // Is the crosshair over a friendly target? + if ( cgs.clientinfo[ cg.crosshairColorClientNum ].team == cgs.clientinfo[ cg.snap->ps.clientNum ].team ) + { + trap_R_SetColor( cg.crosshairFriendRGBA ); + } + } + + w = h = cg_crosshairSize.value; + + // Determine the + scale = ((float)cg.predictedPlayerState.inaccuracy / ((float)weaponData[cg.predictedPlayerState.weapon].attack[ATTACK_NORMAL].maxInaccuracy+1)); + scale = 1 + scale * 1; + + w = w * scale; + h = h * scale; + + { + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + ca = cg_drawCrosshair.integer; + if (ca < 0) + { + ca = 0; + } + + hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; + + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), + y + cg.refdef.y + 0.5 * (cg.refdef.height - h), + w, h, 0, 0, 1, 1, NULL, hShader ); +} + +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd; + vec3_t vright; + vec3_t vup; + float xzi; + float yzi; + +// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution +// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution + xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + + AngleVectors (cg.refdef.viewangles, vfwd, vright, vup); + + VectorSubtract (worldCoord, cg.refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return qfalse; + } + + xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x); + yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +} + +qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int content; + + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); + + cg.crosshairColorClientNum = -1; + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, MASK_SHOT ); + if ( trace.entityNum >= MAX_CLIENTS ) { + return; + } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // People playing a team game cant see the name of people not on their team, unless + // they are spectating. + if ( cgs.gametypeData->teams ) + { + if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) ) + { + if ( cgs.clientinfo[ trace.entityNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] ) + { + return; + } + } + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; + cg.crosshairColorClientNum = trace.entityNum; +} + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) +{ + float *color; + char *name; + float w; + int y; + + if ( !cg_drawCrosshair.integer ) + { + return; + } + + if ( !cg_drawCrosshairNames.integer ) + { + return; + } + + if ( cg.renderingThirdPerson ) + { + return; + } + + // If the player isnt spectating then make sure he cant see enemies names + if ( cgs.gametypeData->teams ) + { + if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) ) + { + if ( cgs.clientinfo[ cg.crosshairClientNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] ) + { + return; + } + } + } + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + name = cgs.clientinfo[ cg.crosshairClientNum ].name; + + if ( cg_drawCrosshairNames.integer == 2 ) // Just below the crosshair + { + y = (SCREEN_HEIGHT / 2) + 20 ; + } + else + { + y = 465 ; + } + + + color[3] *= 0.5f; + w = trap_R_GetTextWidth(name, cgs.media.hudFont, 0.43f, 0); + CG_DrawText( 320 - w / 2, y, cgs.media.hudFont, 0.43f, color, name, 0, 0 ); + + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) +{ + const char* s; + float y; + + if ( cg.scoreBoardShowing ) + { + return; + } + + y = 415; + + // Need to be a ghost or someone following who isnt a spectator to see the respawn time + if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) || + ((cg.snap->ps.pm_flags & PMF_FOLLOW) && cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR) ) + { + if ( cg.predictedPlayerState.respawnTimer ) + { + int time; + time = cg.predictedPlayerState.respawnTimer - cg.time; + time /= 1000; + if ( time < 1 ) + { + time = 1; + } + s = va("RESPAWN IN %i SECONDS", time ); + } + else + { + s = "GHOST"; + } + } + else + s = "SPECTATOR"; + + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.45f, 0 ) / 2, + y, cgs.media.hudFont, 0.45f, colorWhite, s, 0, DT_OUTLINE ); + + // Draw some instructions on the screen for joining the game when the client is a spectator + if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR ) + { + // Make sure they arent following someone and arent a ghost + if ( !(cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) ) + { + s = "press ESC and use the PLAYER menu to play"; + CG_DrawText( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ) / 2, + y + 15, cgs.media.hudFont, 0.43f, colorWhite, s, 0, DT_OUTLINE ); + } + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote(void) +{ + char *s; + int sec; + + if ( !cgs.voteTime ) + { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.voteModified ) + { + cgs.voteModified = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( cgs.voteDuration - ( cg.time - cgs.voteTime ) ) / 1000; + + if ( sec < 0 ) + { + sec = 0; + } + + s = va("VOTE(%i):%s", sec, cgs.voteString ); + CG_DrawText ( 10, 58, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE ); + + s = va("needed:%i yes:%i no:%i", cgs.voteNeeded, cgs.voteYes, cgs.voteNo); + CG_DrawText ( 10, 70, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE ); +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) +{ + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) +{ + const char *s; + + if ( cg.scoreBoardShowing ) + { + return qfalse; + } + + if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) + { + return qfalse; + } + + s = va("following %s", cgs.clientinfo[ cg.snap->ps.clientNum ].name ); + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.63f, 0 ) / 2, + 40, cgs.media.hudFont, 0.63f, colorWhite, s, 0, DT_OUTLINE ); + + CG_DrawSpectator ( ); + + return qtrue; +} + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) +{ + int w; + int sec; + const char *s; + + sec = cg.warmup; + if ( !sec ) + { + return; + } + + if ( sec < 0 ) + { + s = "Waiting for players"; + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ); + CG_DrawText ( 320 - w / 2, 24, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE ); + + cg.warmupCount = 0; + return; + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) + { + cg.warmup = 0; + sec = 0; + } + + s = va( "map restart in: %i", sec + 1 ); + if ( sec != cg.warmupCount ) + { + cg.warmupCount = sec; + } + + w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 ); + CG_DrawText (320 - w / 2, 105, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE ); +} + +/* +================= +CG_DrawChat +================= +*/ +static void CG_DrawChat ( void ) +{ + float w; + int h; + int i; + int chatHeight; + float y; + float x; + + // Grab the users text height but dont let it get bigger than the define + chatHeight = cg_chatHeight.integer; + if (chatHeight > CHAT_HEIGHT ) + { + chatHeight = CHAT_HEIGHT; + } + + // Is the chat enabled right now? + if ( chatHeight <= 0 ) + { + return; + } + + // Nothing to draw + if (cgs.chatLastPos == cgs.chatPos ) + { + return; + } + + // Is it time to stop drawing the current chat message? + if ( cg.time - cgs.chatTime[cgs.chatLastPos % chatHeight] > cg_chatTime.integer) + { + cgs.chatLastPos++; + } + + // Determine how tall the entire chat block is + h = (cgs.chatPos - cgs.chatLastPos) * 15; + + if ( cg.scoreBoardShowing ) + { + y = 395; + x = 50; + + if ( cg.scoreBoardBottom + 10 > y - h ) + { + y = cg.scoreBoardBottom + 10 + h; + } + + if ( y > 480 ) + { + y = 475; + } + } + else + { + y = 395; + x = 35; + } + + // Find the greatest width out of the strings rendered + for (w = 0, i = cgs.chatLastPos; i < cgs.chatPos; i++) + { + float tw = trap_R_GetTextWidth ( cgs.chatText[i % chatHeight], cgs.media.hudFont, 0.43f, 0 ); + + if (tw > w) + { + w = tw; + } + } + + for (i = cgs.chatPos - 1; i >= cgs.chatLastPos ; i--) + { + qhandle_t font = cgs.media.hudFont; + float scale = 0.38f; + + CG_DrawText ( x, y - (cgs.chatPos - i - 1) * 15, + font, scale, colorWhite, cgs.chatText[i % chatHeight], 0, DT_OUTLINE ); + } +} + +/* +================= +CG_DrawTimedMenus +================= +*/ +void CG_DrawTimedMenus() +{ + if (cg.voiceTime) + { + int t = cg.time - cg.voiceTime; + if ( t > 2500 ) + { + Menus_CloseByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "0"); + cg.voiceTime = 0; + } + } +} + +/* +================= +CG_DrawMapChange +================= +*/ +void CG_DrawMapChange ( void ) +{ + const char *s; + int w; + float x; + + // Draw a nice background image + CG_DrawStretchPic ( 0, 0, 640, 480, 0, 0, 1, 1, colorWhite, + trap_R_RegisterShaderNoMip ( "gfx/menus/backdrop/pra1_sof2_logo" ) ); + + s = "Server Changing Maps"; + w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 ); + x = (SCREEN_WIDTH - w) / 2; + CG_DrawText (x, 360, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + s = "Please wait"; + w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 ); + x = (SCREEN_WIDTH - w) / 2; + CG_DrawText (x, 400, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); +} + +/* +================== +CG_DrawTimers + +Draws the round, total, and frozer timers +================== +*/ +void CG_DrawTimers ( void ) +{ + int y; + int x; + + if ( !cg_drawTimer.integer ) + { + return; + } + + y = 435; + x = 120; + + if ( cg.predictedPlayerState.stats[STAT_FROZEN] ) + { + CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, -cg.predictedPlayerState.stats[STAT_FROZEN] ); + } + else if ( cgs.gametypeTimerTime != 0 ) + { + if ( cgs.gametypeTimerTime < cg.time ) + { + return; + } + + CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, cgs.gametypeTimerTime - cg.time ); + } +} + +/* +================== +CG_DrawFlashBang + +Renders and handles the progression of the flashgrenade. The flash grenade is just a white +overlay on the whole screen which fades out over time +================== +*/ +static void CG_DrawFlashBang ( void ) +{ + vec4_t color; + + // Is there an active flash bang? + if ( cg.flashbangTime + cg.flashbangFadeTime <= cg.time ) + { + return; + } + + // Spectators and dead people dont need to see a flash so it can stop here + if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL ) + { + cg.flashbangTime = 0; + return; + } + + VectorCopy ( colorWhite, color ); + color[3] = cg.flashbangAlpha * (1.0f - ((float)(cg.time - cg.flashbangTime) / (float)cg.flashbangFadeTime)); + + color[3] *= 2.0; + if (color[3]<0) + { + color[3]=0; + } + + if (color[3]>1) + { + color[3]=1; + } + + CG_FillRect ( 0, 0, 640, 480, color ); +} + + +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) +{ + static qboolean oldScoreBoardShowing = qfalse; + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) + { + return; + } + + if (cg.mMapChange) + { + CG_DrawMapChange ( ); + return; + } + + if ( cg_draw2D.integer == 0 ) + { + CG_DrawFlashBang ( ); + return; + } + + // Handle the diabling of the console messages when in the scoreboard. enough + // clutter already on the scoreboard without seeing those too. + if ( oldScoreBoardShowing != cg.scoreBoardShowing ) + { + trap_Cvar_Set ( "con_draw", cg.scoreBoardShowing?"0":"1" ); + oldScoreBoardShowing = cg.scoreBoardShowing; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + CG_DrawIntermission(); + CG_DrawChat ( ); + return; + } + + CG_DrawFlashBang ( ); + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + if ( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } + else + { + // don't draw any status if dead or the scoreboard is being explicitly shown + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) + { + Menu_PaintAll(); + + if ( !cg.showScores ) + { + CG_DrawTimedMenus(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } + } + } + + CG_DrawVote(); + + CG_DrawLagometer(); + + if (!cg_paused.integer) + { + CG_DrawUpperRight(); + } + + if ( !CG_DrawFollow() ) + { + CG_DrawWarmup(); + } + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg.scoreBoardShowing) + { + CG_DrawCenterText(); + } + + // Always Draw chat + CG_DrawChat ( ); +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) +{ + float separation; + vec3_t baseOrg; + float parm1, parm2; + + // optionally draw the info screen instead + if ( !cg.snap ) + { + CG_DrawInformation(); + return; + } + + if (cg.mMapChange) + { + CG_DrawMapChange ( ); + return; + } + + // Handle the start of an inf match + if ( cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR && + !(cg.predictedPlayerState.pm_flags & PMF_GHOST) && + !(cg.predictedPlayerState.pm_flags & PMF_FOLLOW) ) + { + if ( cgs.gametypeTimerTime >= cg.time && !cg.predictedPlayerState.stats[STAT_FROZEN] && cgs.gametypeTimerTime != 0 && !cg.gametypeStarted ) + { + CG_CenterPrint( "GO!", 1.1f ); + cg.gametypeStarted = qtrue; + + trap_S_StartLocalSound ( cgs.media.goSound, CHAN_AUTO ); + } + } + + // Popup the objectives scren if we need to + if( cg.popupObjectives ) + { + char temp[MAX_INFO_STRING]; + char lastobjectives[MAX_INFO_STRING]; + + Com_sprintf ( lastobjectives, MAX_INFO_STRING, "%s_%s_%d", cgs.mapname, cgs.gametypeData->name, cgs.gameID ); + trap_Cvar_VariableStringBuffer ( "cg_lastobjectives", temp, MAX_INFO_STRING ); + + if ( Q_stricmp ( temp, lastobjectives ) ) + { + if ( !cgs.gametypeData->description ) + { + // If the client isnt on a team yet and this is a team game, bring up the team dialog + if ( cgs.gametypeData->teams && cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR ) + { + trap_SendConsoleCommand ( "ui_team;" ); + } + // Providing outfitting is available bring up the outfitting dialog + else if ( cgs.pickupsDisabled ) + { + trap_SendConsoleCommand ( "ui_outfitting;" ); + } + } + else + { + // If already on a team then no need to choose a team + if ( cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR ) + { + trap_Cvar_Set ( "ui_info_seenobjectives", "1" ); + } + + trap_SendConsoleCommand ( "ui_objectives;" ); + } + + trap_Cvar_Set ( "cg_lastobjectives", lastobjectives ); + } + + cg.popupObjectives = qfalse; + } + + switch ( stereoView ) + { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + Com_Error( ERR_FATAL, "CG_DrawActive: Undefined stereoView" ); + } + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + // When goggles are on there are a few passes to generate the effect + if ( cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON ) + { + switch(cg.predictedPlayerState.stats[STAT_GOGGLES]) + { + case GOGGLES_NIGHTVISION: + if (cg.mInRMG) + { + parm1 = RMG_distancecull.value; + parm2 = 0.0; + } + else + { + parm1 = 2500; + parm2 = 0.0; + } + break; + case GOGGLES_INFRARED: + parm1 = cgs.mIRSeeThrough; + parm2 = cgs.mIRDist; + break; + default: + parm1 = parm2 = 0.0; + break; + } + + trap_R_DrawVisualOverlay ( cg.predictedPlayerState.stats[STAT_GOGGLES], qtrue, parm1, parm2); + + // draw 3D view + trap_R_RenderScene( &cg.refdef ); + + trap_R_DrawVisualOverlay( cg.predictedPlayerState.stats[STAT_GOGGLES], qfalse, parm1, parm2); + } + // Normal rendering + else + { + trap_R_RenderScene( &cg.refdef ); + } + + // restore original viewpoint if running stereo + if ( separation != 0 ) + { + VectorCopy( baseOrg, cg.refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + diff --git a/code/cgame/cg_drawtools.c b/code/cgame/cg_drawtools.c new file mode 100644 index 0000000..1a2a017 --- /dev/null +++ b/code/cgame/cg_drawtools.c @@ -0,0 +1,421 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, NULL, cgs.media.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, NULL, hShader ); +} + + +/* +================ +CG_DrawStretchPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawStretchPic( + float x, + float y, + float width, + float height, + float sx, + float sy, + float sw, + float sh, + const float* color, + qhandle_t hShader + ) +{ + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, sx, sy, sw, sh, NULL, hShader ); +} + +/* +================ +CG_DrawRotatePic + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +rotates around the upper right corner of the passed in point +================= +*/ +void CG_DrawRotatePic( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawRotatePic( x, y, width, height, 0, 0, 1, 1, angle, hShader ); +} + +/* +================ +CG_DrawRotatePic2 + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +Actually rotates around the center point of the passed in coordinates +================= +*/ +void CG_DrawRotatePic2( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawRotatePic2( x, y, width, height, 0, 0, 1, 1, angle, hShader ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x/64.0; + t1 = y/64.0; + s2 = (x+w)/64.0; + t2 = (y+h)/64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, NULL, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) +{ + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height-1; + left = cg.refdef.x; + right = left + cg.refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2f, 0.2f, 1}; + static vec4_t blue = {0.2f, 0.2f, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( vec4_t color, int health, int armor ) +{ + int count; + int max; + + VectorCopy ( colorWhite, color ); + + color[3] = 1.0f; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) + { + VectorCopy ( colorBlack, color ); + return; + } + + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + + if ( max < count ) + { + count = max; + } + + health += count; + + // set the color based on health + color[0] = 1.0f; + if ( health >= 100 ) + { + color[2] = 1.0f; + } + else if ( health < 66 ) + { + color[2] = 0; + } + else + { + color[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) + { + color[1] = 1.0f; + } + else if ( health < 30 ) + { + color[1] = 0; + } + else + { + color[1] = ( health - 30 ) / 30.0; + } +} + +/* +===================== +CG_DrawText + +Renders text on the screen +===================== +*/ +void CG_DrawText ( float x, float y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags ) +{ + x *= cgs.screenXScale; + y *= cgs.screenYScale; + scale *= (cgs.screenXScale); + + trap_R_DrawText ( (int)x, (int)y, font, scale, color, text, limit, flags ); +} + +/* +===================== +CG_DrawTimer + +Draws a timer on the screen with the given parameters +===================== +*/ +void CG_DrawTimer ( float x, float y, qhandle_t font, float scale, vec4_t color, int flags, int msec ) +{ + const char* s; + + if ( msec ) + { + int mins; + int seconds; + int tens; + qboolean neg; + + // Remember the milliseconds were negative + // before making them positive again + if ( msec < 0 ) + { + msec *= -1; + neg = qtrue; + } + else + { + neg = qfalse; + } + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%s%i:%i%i", neg?"-":"",mins, tens, seconds ); + } + else + { + s = "------"; + } + + CG_DrawText ( x, y, font, scale, color, s, 0, flags ); +} + +/* +===================== +CG_DrawTextWithCursor + +Renders text on the screen with a blinking cursor +===================== +*/ +void CG_DrawTextWithCursor ( float x, float y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags, int cursorPos, char cursor ) +{ + x *= cgs.screenXScale; + y *= cgs.screenYScale; + scale *= (cgs.screenXScale); + + trap_R_DrawTextWithCursor ( (int)x, (int)y, font, scale, color, text, limit, flags, cursorPos, cursor ); +} diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c new file mode 100644 index 0000000..15c458b --- /dev/null +++ b/code/cgame/cg_effects.c @@ -0,0 +1,275 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale (vec, spacing, vec); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*5; + le->pos.trDelta[1] = crandom()*5; + le->pos.trDelta[2] = crandom()*5 + 6; + + VectorAdd (move, vec, move); + } +} + + +void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius) +{ + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leType = LE_LINE; + le->startTime = cg.time; + le->endTime = cg.time + time; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin); + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_LINE; + re->radius = 0.5*radius; + re->customShader = cgs.media.whiteShader; //trap_R_RegisterShaderNoMip("textures/colombia/canvas_doublesided"); + + re->shaderTexCoord[0] = re->shaderTexCoord[1] = 1.0f; + + if (color==0) + { + re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff; + } + else + { + re->shaderRGBA[0] = color & 0xff; + color >>= 8; + re->shaderRGBA[1] = color & 0xff; + color >>= 8; + re->shaderRGBA[2] = color & 0xff; + re->shaderRGBA[3] = 0xff; + } + + le->color[3] = 1.0; +} + +/* +================== +CG_ThrowShard +================== +*/ +void CG_ThrowShard( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + le->angles.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + VectorSet(le->angles.trBase, 20, 20, 20); + VectorCopy( velocity, le->angles.trDelta ); + le->pos.trTime = cg.time; + le->angles.trTime = cg.time; + + le->leFlags = LEF_TUMBLE; + + le->angles.trBase[YAW] = 180; + + le->bounceFactor = 0.3f; +} + +/* +================== +CG_GlassShatter +Throws glass shards from within a given bounding box in the world +================== +*/ +void CG_GlassShatter(int entnum, vec3_t org, vec3_t mins, vec3_t maxs) +{ + vec3_t velocity, a, shardorg, dif, difx; + float windowmass; + float shardsthrow = 0; + + trap_S_StartSound(org, entnum, CHAN_BODY, cgs.media.glassBreakSound, -1, -1 ); + + VectorSubtract(maxs, mins, a); + + windowmass = VectorLength(a); //should give us some idea of how big the chunk of glass is + + while (shardsthrow < windowmass) + { + velocity[0] = crandom()*150; + velocity[1] = crandom()*150; + velocity[2] = 150 + crandom()*75; + + VectorCopy(org, shardorg); + + dif[0] = (maxs[0]-mins[0])/2; + dif[1] = (maxs[1]-mins[1])/2; + dif[2] = (maxs[2]-mins[2])/2; + + if (dif[0] < 2) + { + dif[0] = 2; + } + if (dif[1] < 2) + { + dif[1] = 2; + } + if (dif[2] < 2) + { + dif[2] = 2; + } + + difx[0] = (float) Q_irand(1, (int)((dif[0]*0.9)*2)); + difx[1] = (float) Q_irand(1, (int)((dif[1]*0.9)*2)); + difx[2] = (float) Q_irand(1, (int)((dif[2]*0.9)*2)); + + if (difx[0] > dif[0]) + { + shardorg[0] += difx[0]-(dif[0]); + } + else + { + shardorg[0] -= difx[0]; + } + if (difx[1] > dif[1]) + { + shardorg[1] += difx[1]-(dif[1]); + } + else + { + shardorg[1] -= difx[1]; + } + if (difx[2] > dif[2]) + { + shardorg[2] += difx[2]-(dif[2]); + } + else + { + shardorg[2] -= difx[2]; + } + + trap_FX_PlayEffectID( cgs.media.glassChunkEffect, shardorg, velocity, -1, -1 ); + + shardsthrow += 20; + } +} + +qboolean CG_PointLineIntersect ( vec3_t start, vec3_t end, vec3_t point, float rad, vec3_t intersection ) +{ + vec3_t dir; + vec3_t distance; + float len; + float lineSize; + + VectorSubtract ( end, start, dir ); + lineSize = VectorNormalize ( dir ); + + // Calculate the distnace from the shooter to the target + VectorSubtract ( point, start, distance ); + + // Use that distnace to determine the point of tangent in relation to + // the center of the player entity + VectorMA ( start, DotProduct ( dir, distance ), dir, intersection ); + + VectorSubtract ( intersection, point, distance ); + len = VectorLengthSquared ( distance ); + + // Is the intersection point within the given radius requirements? + if ( len < rad * rad ) + { + // Make sure its not past the end of the given line + VectorSubtract ( intersection, start, distance ); + len = VectorLengthSquared ( distance ); + + // If the len + if ( len < lineSize * lineSize ) + { + return qtrue; + } + } + + return qfalse; +} + +void CG_BulletFlyBySound ( vec3_t start, vec3_t end ) +{ + // make the incidental sounds - all of these should already be precached on the server + vec3_t soundPoint; + if( CG_PointLineIntersect(start, end, cg.refdef.vieworg, 100, soundPoint ) ) + { + if (irand(1, 10) < 5) + { + int which = irand(0, NUMFLYBYS - 1); + + trap_S_StartSound (soundPoint, cg.predictedPlayerState.clientNum, CHAN_AUTO, cgs.media.flybySounds[which], -1, -1 ); + } + } +} diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c new file mode 100644 index 0000000..010f5a0 --- /dev/null +++ b/code/cgame/cg_ents.c @@ -0,0 +1,1049 @@ +// Copyright (C) 2001-2002 Raven Software +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" +#include "..\game\q_shared.h" +#include "..\ghoul2\g2.h" + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +float *CG_SetEntitySoundPosition( centity_t *cent ) +{ + static vec3_t v3Return; + + if ( cent->currentState.solid == SOLID_BMODEL ) + { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + VectorCopy(origin, v3Return); + } + else + { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + VectorCopy(cent->lerpOrigin, v3Return); + } + + return v3Return; +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) +{ + sfxHandle_t hSFX; + float *v3Origin; + + // update sound origins + v3Origin = CG_SetEntitySoundPosition(cent); + + // add loop sound + if ( cent->currentState.loopSound ) + { + // ambient soundset string index valid? + if (cent->currentState.eType == ET_MOVER && cent->currentState.mSoundSet) + { + hSFX = trap_AS_GetBModelSound( CG_ConfigString( CS_AMBIENT_SOUNDSETS + cent->currentState.mSoundSet), cent->currentState.loopSound); + + if (hSFX == -1) + { + Com_Printf("CG_EntityEffects(): Bad SFX handle -1, ambient soundset not loaded?\n"); + } + else + { + trap_S_AddLoopingSound( cent->currentState.number, v3Origin/*cent->lerpOrigin*/, vec3_origin, hSFX, CHAN_AUTO ); + } + } + else + { + if (cent->currentState.eType != ET_SPEAKER) + { + trap_S_AddLoopingSound( + cent->currentState.number, + cent->lerpOrigin, + vec3_origin, + 0, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + else + { + trap_S_AddRealLoopingSound( + cent->currentState.number, + cent->lerpOrigin, + vec3_origin, + ((float)cent->currentState.time2) / 1000.0f, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + } + } +} + +/* +================== +CG_SetGhoul2Info +================== +*/ +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent) +{ + ent->ghoul2 = cent->ghoul2; + VectorCopy( cent->modelScale, ent->modelScale); + ent->radius = cent->radius + 256; + VectorCopy (cent->lerpAngles, ent->angles); +} + +/* +================== +CG_SetGhoul2Info + +Position an entity based on the bolt point of a ghoul2 model +================== +*/ +qboolean G2_PositionEntityOnBolt ( + refEntity_t *ent, + void *ghoul2, + int modelIndex, + int boltIndex, + vec3_t origin, + vec3_t angles, + vec3_t modelScale + ) +{ + qboolean boltMatrixOK = qfalse; + mdxaBone_t boltMatrix; + + if (boltIndex < 0) + return boltMatrixOK; + + boltMatrixOK = trap_G2API_GetBoltMatrix(ghoul2, modelIndex, boltIndex, &boltMatrix, + angles, origin, cg.time, cgs.gameModels, modelScale); + + if (boltMatrixOK) + { + // set up the axis and origin + ent->origin[0] = boltMatrix.matrix[0][3]; + ent->origin[1] = boltMatrix.matrix[1][3]; + ent->origin[2] = boltMatrix.matrix[2][3]; + + ent->axis[0][0] = boltMatrix.matrix[0][0]; + ent->axis[0][1] = boltMatrix.matrix[1][0]; + ent->axis[0][2] = boltMatrix.matrix[2][0]; + + ent->axis[1][0] = boltMatrix.matrix[0][1]; + ent->axis[1][1] = boltMatrix.matrix[1][1]; + ent->axis[1][2] = boltMatrix.matrix[2][1]; + + ent->axis[2][0] = boltMatrix.matrix[0][2]; + ent->axis[2][1] = boltMatrix.matrix[1][2]; + ent->axis[2][2] = boltMatrix.matrix[2][2]; + } + + return boltMatrixOK; +} + +/* +================== +CG_ScaleModelAxis +================== +*/ +void CG_ScaleModelAxis(refEntity_t *ent) +{ + // scale the model should we need to + if (ent->modelScale[0] && ent->modelScale[0] != 1.0f) + { + VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[1] && ent->modelScale[1] != 1.0f) + { + VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[2] && ent->modelScale[2] != 1.0f) + { + VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] ); + ent->nonNormalizedAxes = qtrue; + } +} + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // Dont draw any player models when the round has ended + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + return; + } + + // if set to invisible, skip + if ((!s1->modelindex) && !(trap_G2_HaveWeGhoul2Models(cent->ghoul2))) + { + return; + } + + memset (&ent, 0, sizeof(ent)); + + // set frame + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + CG_SetGhoul2Info(&ent, cent); + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) + { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // Bodies need shadows under them + if ( cent->currentState.eType == ET_BODY && !(cent->currentState.eFlags&EF_NOSHADOW)) + { + CG_PlayerShadow( cent, &ent.shadowPlane ); + + if ( cg_shadows.integer == 3 && ent.shadowPlane ) + { + ent.renderfx |= RF_SHADOW_PLANE; + } + } + + ent.renderfx |= RF_MINLIGHT; + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) +{ + if ( ! cent->currentState.clientNum ) + { + // not auto triggering + return; + } + + if ( cg.time < cent->miscTime ) + { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm], -1, -1 ); + + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float frac; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) + { + Com_Error( ERR_FATAL, "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( (!es->modelindex ) || ( es->eFlags & EF_NODRAW ) ) + { + return; + } + + if ( !cg_items[es->modelindex].registered ) + { + CG_RegisterItemVisuals ( es->modelindex); + } + + item = &bg_itemlist[ es->modelindex ]; + + // Track gametype items for the radar + if ( es->modelindex >= MODELINDEX_GAMETYPE_ITEM && es->modelindex <= MODELINDEX_GAMETYPE_ITEM_2 ) + { + cg.radarEntities[cg.radarEntityCount++] = cent; + } + + // Simple items draws sprites only + if ( cg_simpleItems.integer && item->giType != IT_GAMETYPE && cg_items[es->modelindex].mSimpleIcon) + { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 8; + ent.customShader = cg_items[es->modelindex].mSimpleIcon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(&ent); + return; + } + + memset (&ent, 0, sizeof(ent)); + + if (cent->currentState.eFlags & EF_ANGLE_OVERRIDE) + { + AnglesToAxis ( cent->currentState.angles, ent.axis ); + } + else + { + vec3_t angles; + + VectorCopy ( cent->currentState.angles, angles ); + if ( item->giType == IT_WEAPON ) + { + if ( item->giTag == WP_MM1_GRENADE_LAUNCHER ) + { + angles[ROLL] += 135; + cent->lerpOrigin[2] += 4; + } + else + { + angles[ROLL] += 90; + } + } + + AnglesToAxis ( angles, ent.axis ); + } + + wi = NULL; + + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if (!(cent->currentState.eFlags & EF_ANGLE_OVERRIDE)) + { + if ( item->giType == IT_WEAPON ) + { + wi = &cg_weapons[item->giTag]; + cent->lerpOrigin[0] -= + wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= + wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= + wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + } + + // an extra height boost + if ( item->giType == IT_WEAPON ) + { + cent->lerpOrigin[2] -= 14; + } + else + { + cent->lerpOrigin[2] -= 15; + } + } + + ent.hModel = cg_items[es->modelindex].models[0]; + ent.ghoul2 = cg_items[es->modelindex].g2Models[0]; + ent.radius = cg_items[es->modelindex].radius[0]; + + if ( ent.radius <= 0 ) + { + ent.radius = 256; + } + + VectorCopy (cent->lerpAngles, ent.angles); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + frac = 1.0; + + // items without glow textures need to keep a minimum light value + // so they are always visible +// if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR ) || (item->giType == IT_GAMETYPE ) || +// ( item->giType == IT_HEALTH) ) + { + ent.renderfx |= RF_MINLIGHT; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + const attackInfo_t *attack; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) + { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + if ( s1->weapon == WP_KNIFE ) + { + cent->lerpAngles[ROLL] += (float)cg.time * 1.75f; + } + + // Determine which attack to use + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + attack = &weapon->attack[ATTACK_ALTERNATE]; + } + else + { + attack = &weapon->attack[ATTACK_NORMAL]; + } + + // add trails + if ( attack->missileTrailFunc ) + { + if ( cg.time > cent->miscTime ) + { + attack->missileTrailFunc( cent, s1->weapon ); + cent->miscTime = cg.time + 25; + } + } + + // add dynamic light + if ( attack->missileDlight ) + { + trap_R_AddLightToScene(cent->lerpOrigin, + attack->missileDlight, + attack->missileDlightColor[0], + attack->missileDlightColor[1], + attack->missileDlightColor[2] ); + } + + // add missile sound + if ( attack->missileSound ) + { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, 500, attack->missileSound ); + } + + //Don't draw something without a model + if (!trap_G2_HaveWeGhoul2Models( attack->missileG2Model) ) + { + return; + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + if (0 == cent->modelScale[0] && 0 == cent->modelScale[1] && 0 == cent->modelScale[2]) + { + VectorSet( cent->modelScale, 1, 1, 1); + } + + CG_SetGhoul2Info(&ent, cent); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.renderfx = RF_NOSHADOW; + ent.ghoul2 = attack->missileG2Model; + + // spin as it moves + if ( !(s1->eFlags&EF_ANGLE_OVERRIDE) && s1->apos.trType != TR_INTERPOLATE ) + { + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) + { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) + { + RotateAroundDirection( ent.axis, cg.time * 0.25f ); + } + else + { + RotateAroundDirection( ent.axis, (float)s1->time ); + } + } + else + { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + CG_SetGhoul2Info(&ent, cent); + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) + { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } + else + { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) + { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + trap_R_AddRefEntityToScene(&ent); + } +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + CG_SetGhoul2Info(&ent, cent); + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->gametypeitems; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum/256.0 * 360; // roll offset + + CG_SetGhoul2Info(&ent, cent); + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( + const vec3_t in, + int moverNum, + int fromTime, + int toTime, + vec3_t out + ) +{ + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) + { + VectorCopy( in, out ); + return; + } + + cent = CG_GetEntity ( moverNum ); + if ( cent->currentState.eType != ET_MOVER ) + { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) +{ + vec3_t current; + vec3_t next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) + { + Com_Error( ERR_FATAL, "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectoryDelta( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + cent->lerpVelocity[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpVelocity[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpVelocity[2] = current[2] + f * ( next[2] - current[2] ); + + cent->lerpLeanOffset = cent->currentState.leanOffset + (int)(f * (float)(cent->nextState.leanOffset - cent->currentState.leanOffset)) - LEAN_OFFSET; +} + +/* +=============== +CG_CalcEntityLerpPositions +=============== +*/ +void CG_CalcEntityLerpPositions( centity_t *cent ) +{ + // if this player does not want to see extrapolated players + if ( !cg_smoothClients.integer ) + { + // make sure the clients use TR_INTERPOLATE + if ( cent->currentState.number < MAX_CLIENTS ) + { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) + { + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if ( cent->interpolate && + cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS ) + { + CG_InterpolateEntityPosition( cent ); + return; + } + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + VectorCopy ( cent->currentState.pos.trDelta, cent->lerpVelocity ); + cent->lerpLeanOffset = cent->currentState.leanOffset - LEAN_OFFSET; + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent->currentState.number != cg.predictedPlayerState.clientNum ) + { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_DebugCylinder +=============== +*/ +void CG_DebugCylinder ( centity_t* cent ) +{ + refEntity_t ref; + vec3_t normal; + + memset( &ref, 0, sizeof(ref) ); + VectorCopy ( cent->currentState.origin, ref.origin ); + VectorCopy ( cent->currentState.origin, ref.oldorigin ); + ref.origin[2] += 100; + ref.radius = cent->currentState.time2; + ref.rotation = cent->currentState.time2 + 10; + ref.customShader = trap_R_RegisterShader( "line" ); + ref.reType = RT_CYLINDER; + + ref.shaderRGBA[0] = (unsigned char) (255.0f * g_color_table[ColorIndex(cent->currentState.time)][0]); + ref.shaderRGBA[1] = (unsigned char) (255.0f * g_color_table[ColorIndex(cent->currentState.time)][1]); + ref.shaderRGBA[2] = (unsigned char) (255.0f * g_color_table[ColorIndex(cent->currentState.time)][2]); + ref.shaderRGBA[3] = 255; + + VectorSubtract(ref.oldorigin, ref.origin, normal); + VectorNormalize(normal); + VectorCopy( normal, ref.axis[0] ); + RotateAroundDirection( ref.axis, 0); + + trap_R_AddRefEntityToScene ( &ref ); +} + +static void CG_AddLocalSet( centity_t *cent ) +{ + cent->ambientSetTime = trap_AS_AddLocalSet( CG_ConfigString( CS_AMBIENT_SOUNDSETS + cent->currentState.mSoundSet/*localSoundSet*/), cg.refdef.vieworg, cent->lerpOrigin, cent->currentState.number, cent->ambientSetTime ); +} + +/* +=============== +CG_AddCEntity +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) +{ + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) + { + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + // add local sound set if any + if ( cent->currentState.mSoundSet && cent->currentState.eType != ET_MOVER ) + { + CG_AddLocalSet( cent ); + } + + // do this before we copy the data to refEnts + if (trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + trap_G2_SetGhoul2ModelIndexes(cent->ghoul2, cgs.gameModels, cgs.skins); + } + + switch ( cent->currentState.eType ) + { + default: + Com_Error( ERR_FATAL, "Bad entity type: %i\n", cent->currentState.eType ); + break; + + case ET_DAMAGEAREA: + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + case ET_TERRAIN: + break; + + case ET_GENERAL: + CG_General( cent ); + break; + + case ET_PLAYER: + CG_Player( cent ); + break; + + case ET_ITEM: + CG_Item( cent ); + break; + + case ET_MISSILE: + CG_Missile( cent ); + break; + + case ET_MOVER: + CG_Mover( cent ); + break; + + case ET_BEAM: + CG_Beam( cent ); + break; + + case ET_PORTAL: + CG_Portal( cent ); + break; + + case ET_SPEAKER: + CG_Speaker( cent ); + break; + + case ET_BODY: + + // If the bodies are to stay around forever and there + // is a body time set then see if we should hide it + if ( cg_bodyTime.integer && cent->currentState.time2 ) + { + int time = cg_bodyTime.integer * 1000; + + if ( time < BODY_SINK_DELAY ) + { + time = BODY_SINK_DELAY; + } + + time += cent->currentState.time2; + + // Body is long gone + if ( cg.time > time + BODY_SINK_TIME ) + { + return; + } + + // Sink the body + if ( cg.time > time ) + { + cent->lerpOrigin[2] -= ((float)(cg.time - time) / (float)BODY_SINK_TIME) * (BODY_SINK_TIME/100.0f); + + // Hack to stop shadows from showing up + cent->currentState.eFlags |= EF_NOSHADOW; + } + } + + // Dont show your own dead body unless in third person + if ( !cg.renderingThirdPerson ) + { + if ( cg.snap->ps.clientNum == cent->currentState.otherEntityNum ) + { + if ( cg_entities[cg.snap->ps.clientNum].currentState.eFlags & EF_DEAD ) + { + return; + } + } + } + + CG_General( cent ); + break; + + case ET_DEBUG_CYLINDER: + CG_DebugCylinder ( cent ); + break; + } +} + +/* +=============== +CG_AddPacketEntities +=============== +*/ +void CG_AddPacketEntities( void ) +{ + int num; + centity_t *cent; + + // set cg.frameInterpolation + if ( cg.nextSnap ) + { + int delta; + + delta = (cg.nextSnap->serverTime - cg.snap->serverTime); + if ( delta == 0 ) + { + cg.frameInterpolation = 0; + } + else + { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } + else + { + // actually, it should never be used, because + // no entities should be marked as interpolating + cg.frameInterpolation = 0; + } + + // the auto-rotating items will all have the same axis + cg.autoAngles[0] = 0; + cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[2] = 0; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[2] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + + // add in the Ghoul2 stuff. +// VectorCopy( cg_entities[ cg.snap->ps.clientNum].modelScale, cg.predictedPlayerEntity.modelScale); +// cg.predictedPlayerEntity.radius = cg_entities[ cg.snap->ps.clientNum].radius; + +// num = cg_entitiescg.predictedPlayerEntity.currentState.number; + BG_PlayerStateToEntityState( &cg.predictedPlayerState, + &cg_entities[cg.predictedPlayerState.clientNum].currentState, + qfalse ); + +/* + // If the client number changes then we have to blow away our current model + if ( num != cg.predictedPlayerEntity.currentState.number ) + { + trap_G2API_CleanGhoul2Models ( &cg.predictedPlayerEntity.ghoul2 ); + cg.predictedPlayerEntity.pe.weaponModelSpot = 0; + cg.predictedPlayerEntity.pe.weapon = 0; + cg.predictedPlayerEntity.ghoul2 = NULL; + } +*/ + + cg_entities[cg.predictedPlayerState.clientNum].interpolate = qfalse; + CG_AddCEntity( &cg_entities[cg.predictedPlayerState.clientNum] ); + + // lerp the non-predicted value for lightning gun origins +// CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // Reset radar entities + cg.radarEntityCount = 0; + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) + { + // Don't re-add ents that have been predicted. + if (cg.snap->entities[ num ].number != cg.snap->ps.clientNum) + { + cent = CG_GetEntity ( cg.snap->entities[ num ].number ); + CG_AddCEntity( cent ); + } + } + + for(num=0;numcurrentValid) + { + CG_AddCEntity( cent ); + } + } +} + diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c new file mode 100644 index 0000000..40adae5 --- /dev/null +++ b/code/cgame/cg_event.c @@ -0,0 +1,1195 @@ +// Copyright (C) 2001-2002 Raven Software +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" +#include "..\ghoul2\g2.h" +#include "../../ui/menudef.h" + +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = "Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = "11th"; + } else if ( rank == 12 ) { + s = "12th"; + } else if ( rank == 13 ) { + s = "13th"; + } else if ( rank % 10 == 1 ) { + s = va("%ist", rank); + } else if ( rank % 10 == 2 ) { + s = va("%ind", rank); + } else if ( rank % 10 == 3 ) { + s = va("%ird", rank); + } else { + s = va("%ith", rank); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_GameOver +============= +*/ +static void CG_GameOver ( entityState_t *ent ) +{ + switch ( ent->eventParm ) + { + case GAME_OVER_TIMELIMIT: + Com_sprintf ( cgs.gameover, MAX_QPATH, "Timelimit Hit" ); + break; + + case GAME_OVER_SCORELIMIT: + if ( cgs.gametypeData->teams ) + { + switch ( ent->otherEntityNum ) + { + case TEAM_RED: + Com_sprintf ( cgs.gameover, MAX_QPATH, "Red Team hit the score limit" ); + break; + + case TEAM_BLUE: + Com_sprintf ( cgs.gameover, MAX_QPATH, "Blue Team hit the score limit" ); + break; + } + } + else + { + Com_sprintf ( cgs.gameover, MAX_QPATH, "%s" S_COLOR_WHITE " hit the score limit", cgs.clientinfo[ent->otherEntityNum].name ); + } + break; + + default: + return; + } + + CG_CenterPrint ( cgs.gameover, 0.43f ); + + Com_Printf ( "@%s\n", cgs.gameover ); +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) +{ + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + const char *targetColor; + const char *attackerColor; + attackType_t attack; + gender_t gender; + clientInfo_t *ci; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm & 0xFF; + attack = (ent->eventParm >> 8) & 0xFF; + attackerColor = S_COLOR_WHITE; + targetColor = S_COLOR_WHITE; + + if ( target < 0 || target >= MAX_CLIENTS ) + { + Com_Error( ERR_FATAL, "CG_Obituary: target out of range" ); + } + + // Play the death sound, water if they drowned + if ( mod == MOD_WATER ) + { + trap_S_StartSound ( NULL, target, CHAN_AUTO, cgs.media.drownDeathSound, -1, -1 ); + } + else + { + trap_S_StartSound( NULL, target, CHAN_VOICE, CG_CustomPlayerSound(target, SOUND_DIE_1 + (cg.time%3)), -1, -1); + } + + // Play the frag sound, and make sure its not played more than every 250ms + if ( cg.time - cg.lastKillTime > 250 && attacker == cg.snap->ps.clientNum ) + { + if ( cg_soundFrag.integer ) + { + // If the attacker killed themselves play the selffrag sound + if ( attacker == target ) + { + trap_S_StartLocalSound ( cgs.media.fragSelfSound, CHAN_AUTO ); + } + else + { + // In a team game a kill of a teammate will play the self frag sound rather + // than the frag sound + if ( cgs.gametypeData->teams ) + { + if ( cgs.clientinfo[target].team == cgs.clientinfo[attacker].team ) + { + trap_S_StartLocalSound ( cgs.media.fragSelfSound, CHAN_AUTO ); + } + else + { + trap_S_StartLocalSound ( cgs.media.fragSound, CHAN_AUTO ); + } + } + else + { + trap_S_StartLocalSound ( cgs.media.fragSound, CHAN_AUTO ); + } + } + } + + cg.lastKillTime = cg.time; + } + + ci = &cgs.clientinfo[target]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) + { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } + else + { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); + strcat( targetName, S_COLOR_WHITE ); + + switch ( cgs.clientinfo[target].team ) + { + case TEAM_RED: + targetColor = S_COLOR_RED; + break; + + case TEAM_BLUE: + targetColor = S_COLOR_BLUE; + break; + } + + message2 = ""; + + // check for single client messages + + gender = ci->gender; + + switch( mod ) + { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + if ( gender == GENDER_FEMALE ) + message = "fell to her death"; + else + message = "fell to his death"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + case MOD_TEAMCHANGE: + return; + + default: + message = NULL; + break; + } + + // Attacker killed themselves. Ridicule them for it. + if (attacker == target) + { + switch (mod) + { + case MOD_MM1_GRENADE_LAUNCHER: + case MOD_RPG7_LAUNCHER: + case MOD_M67_GRENADE: + case MOD_M84_GRENADE: + case MOD_F1_GRENADE: + case MOD_L2A2_GRENADE: + case MOD_MDN11_GRENADE: + case MOD_SMOHG92_GRENADE: + case MOD_ANM14_GRENADE: + case MOD_M15_GRENADE: + if ( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if ( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + + default: + if ( gender == GENDER_FEMALE ) + message = "killed herself"; + else if ( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if (message) + { + Com_Printf( "%s%s %s.\n", targetColor, targetName, message); + return; + } + + // check for kill messages from the current clientNum when + // not in a team game. + if ( cgs.gametypeData->showKills ) + { + if ( attacker == cg.snap->ps.clientNum ) + { + char *s; + + if ( !cgs.gametypeData->teams ) + { + s = va("You killed %s%s\n%s place with %i", targetColor, targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + } + else + { + s = va("You killed %s%s", targetColor, targetName ); + } + + CG_CenterPrint( s, 0.43f ); + } + } + + + // check for double client messages + if ( !attackerInfo ) + { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } + else + { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) + { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + + switch ( cgs.clientinfo[attacker].team ) + { + case TEAM_RED: + attackerColor = S_COLOR_RED; + break; + + case TEAM_BLUE: + attackerColor = S_COLOR_BLUE; + break; + } + } + + + if ( attacker != ENTITYNUM_WORLD ) + { + switch (mod) + { + case MOD_KNIFE: + message = "was sliced by"; + break; + + case MOD_USAS_12_SHOTGUN: + case MOD_M590_SHOTGUN: + if ( attack == ATTACK_ALTERNATE ) + { + message = "was bludgeoned by"; + } + else + { + message = "was pumped full of lead by"; + } + break; + + case MOD_M1911A1_PISTOL: + case MOD_USSOCOM_PISTOL: + if ( attack == ATTACK_ALTERNATE ) + { + message = "was pistol whipped by"; + } + else + { + message = "was shot by"; + } + break; + + case MOD_AK74_ASSAULT_RIFLE: + if ( attack == ATTACK_ALTERNATE ) + { + message = "was stabbed by"; + } + else + { + message = "was shot by"; + } + break; + + case MOD_M60_MACHINEGUN: + case MOD_MICRO_UZI_SUBMACHINEGUN: + case MOD_M3A1_SUBMACHINEGUN: + case MOD_M4_ASSAULT_RIFLE: + message = "was shot by"; + break; + + case MOD_MSG90A1_SNIPER_RIFLE: + message = "was sniped by"; + break; + + case MOD_MM1_GRENADE_LAUNCHER: + case MOD_RPG7_LAUNCHER: + case MOD_M67_GRENADE: + case MOD_M84_GRENADE: + case MOD_F1_GRENADE: + case MOD_L2A2_GRENADE: + case MOD_MDN11_GRENADE: + case MOD_SMOHG92_GRENADE: + case MOD_ANM14_GRENADE: + case MOD_M15_GRENADE: + message = "was detonated by"; + break; + + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + + default: + message = "was killed by"; + break; + } + + if (message) { + Com_Printf( "%s%s %s %s%s%s\n", targetColor, targetName, message, attackerColor, attackerName, message2); + return; + } + } + + // we don't know what it was + Com_Printf( "%s%s died.\n", targetColor, targetName ); +} + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum, qboolean autoswitch ) +{ + cg.itemPickup = itemNum; + + // see if it should be the grabbed weapon + if ( cg_autoswitch.integer && bg_itemlist[itemNum].giType == IT_WEAPON && autoswitch ) + { + if ( cg_autoswitch.integer >= 2 ) + { + if ( weaponData[bg_itemlist[itemNum].giTag].safe ) + { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + else + { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + + Com_Printf ( "You picked up %s %s!\n", bg_itemlist[itemNum].pickup_prefix, bg_itemlist[itemNum].pickup_name ); +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) +{ + ECustomSounds sound; + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if (health <= 0 ) + { + return; + } + else if ( health < 25 ) + { + sound = SOUND_PAIN_1; + } + else if ( health < 50 ) + { + sound = SOUND_PAIN_2; + } + else if ( health < 75 ) + { + sound = SOUND_PAIN_3; + } + else + { + sound = SOUND_PAIN_3; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomPlayerSound( cent->currentState.number, sound ), -1, -1 ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + + +static void CG_BodyQueueCopy(centity_t *cent, int clientNum, int hitLocation, vec3_t direction ) +{ + centity_t *source; + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + clientInfo_t *ci; + int i; + + if (cent->ghoul2) + { + trap_G2API_CleanGhoul2Models(¢->ghoul2); + cent->ghoul2 = 0; + } + + if (clientNum < 0 || clientNum >= MAX_CLIENTS) + { + return; + } + + source = CG_GetEntity ( clientNum ); + + ci = &cgs.clientinfo[ clientNum ]; + + cent->radius = 100; + + if (!source) + { + return; + } + + // Make sure the player model is updated before copying it to the body queue + CG_UpdatePlayerModel ( source ); + + if (!source->ghoul2) + { + // some how we don't have a g2 model, so don't do anything + return; + } + + // Remove the weapon bolt for the death + if ( source->pe.weaponModelSpot ) + { + trap_G2API_DetachG2Model ( source->ghoul2, source->pe.weaponModelSpot ); + trap_G2API_RemoveGhoul2Model ( &source->ghoul2, source->pe.weaponModelSpot ); + source->pe.weaponModelSpot = 0; + + source->flashBoltInterface.isValid = qfalse; + source->ejectBoltInterface.isValid = qfalse; + } + + trap_G2API_DuplicateGhoul2Instance(source->ghoul2, ¢->ghoul2); + + // Reset all mision bolt positions + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + ci->boltGametypeItems[i] = -1; + } + + ci->boltNightvision = -1; + + // Clear the source's ghoul2 model to force it to be re-duplicatd. This will + // then cause all the gore attached to it to be cleared + trap_G2API_CleanGhoul2Models ( &source->ghoul2 ); + source->ghoul2 = NULL; + + if (cg_lockDeaths.integer) + { + anim = &ci->animations[ BOTH_DEATH_CHEST_1 ]; + } + else + { + anim = &ci->animations[ cent->currentState.torsoAnim & ~(ANIM_TOGGLEBIT) ]; + } + animSpeed = 50.0f / anim->frameLerp; + + // Clear any bone angles + + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time ); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time ); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels,0, cg.time ); + + // Set the death animation + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 150); + + // hit location is a bit field and we need to iterate through the bits + for ( i = 0; (1<currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) + { + Com_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + // Ignore all events until map is done changing + if ( cg.mMapChange ) + { + return; + } + + if ( !event ) + { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) + { + clientNum = 0; + } + + ci = &cgs.clientinfo[ clientNum ]; + + switch ( event ) + { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) + { + trap_S_StartSound (NULL, es->number, CHAN_BODY, trap_MAT_GetSound(MAT_FOOTSTEP_NORMAL, (es->eventParm&MATERIAL_MASK)), 180, 1000 ); + } + break; + + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + break; + + case EV_FALL_SHORT: + DEBUGNAME("EV_FALL_SHORT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_NORMAL, es->eventParm&MATERIAL_MASK), 150, 900 ); + if ( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + + case EV_FALL_MEDIUM: + DEBUGNAME("EV_FALL_MEDIUM"); + // use normal pain sound + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_PAIN, (es->eventParm>>8)&MATERIAL_MASK), 150, 900 ); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomPlayerSound( es->number, SOUND_PAIN_3 ), -1, -1 ); + if ( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_LAND_DEATH, (es->eventParm>>8)&MATERIAL_MASK), -1, -1 ); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomPlayerSound( cent->currentState.number, SOUND_PAIN_2 ), 150, 900 ); + + // don't play a pain sound right after this + cent->pe.painTime = cg.time; + + if ( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + { + float oldStep; + int delta; + int step; + + DEBUGNAME("EV_STEP"); + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + + if (delta < STEP_TIME) + { + oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; + } + else + { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + + if ( cg.stepChange > MAX_STEP_CHANGE ) + { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + + break; + } + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ), -1, -1 ); + break; + + case EV_WATER_FOOTSTEP: + DEBUGNAME("EV_WATER_FOOTSTEP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterFootstep[rand()%2], -1, -1); + break; + + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterFootstep[rand()%2], -1, -1); + break; + + case EV_WATER_LAND: + DEBUGNAME("EV_WATER_LAND"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterJumpIn, -1, -1 ); + break; + + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterLeave, -1, -1 ); + break; + + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.waterWade[rand()%2], -1, -1 ); + break; + + case EV_ITEM_PICKUP: + { + gitem_t *item; + int index; + qboolean autoswitch = qfalse; + + DEBUGNAME("EV_ITEM_PICKUP"); + + // Dtermine if this item should autoswitch + autoswitch = (es->eventParm & ITEM_AUTOSWITCHBIT)?qtrue:qfalse; + + // player predicted index + index = es->eventParm & ~ITEM_AUTOSWITCHBIT; + + if ( index < 1 || index >= bg_numItems ) + { + break; + } + + item = &bg_itemlist[ index ]; + + if ( item->pickup_sound ) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ), -1, -1 ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) + { + if ( cg.predictedPlayerState.pm_type == PM_NORMAL ) + { + CG_ItemPickup( index, autoswitch ); + } + } + + break; + } + + //================================================================= + // + // weapon events + // + //================================================================= + case EV_CHANGE_WEAPON_CANCELLED: + case EV_CHANGE_WEAPON: + + DEBUGNAME("EV_CHANGE_WEAPON"); + + // Determine whether or not the alt fire popup should show up + if(es->number==cg.snap->ps.clientNum && cg.weaponMenuUp ) + { + // done with weapon menu + cg.weaponMenuUp = qfalse; + + cg.weaponSelect = cg.weaponMenuSelect; + } + + break; + + case EV_READY_WEAPON: + DEBUGNAME("EV_READY_WEAPON"); + break; + + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + CG_FireWeapon( cent, ATTACK_NORMAL ); + break; + + case EV_ALT_FIRE: + DEBUGNAME("EV_ALT_FIRE"); + CG_FireWeapon( cent, ATTACK_ALTERNATE ); + break; + + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); + + if(es->number==cg.snap->ps.clientNum) + { + CG_OutOfAmmoChange( es->eventParm ); + } + break; + + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + break; + + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.itemRespawnSound, -1, -1 ); + cent->miscTime = cg.time; + break; + + //================================================================= + // + // other events + // + //================================================================= + + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + if ( cgs.gametypeData->respawnType != RT_NONE ) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound, -1, -1 ); + } + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + if ( rand() & 1 ) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_BOUNCEMETAL_1, (es->eventParm&MATERIAL_MASK)), -1, -1 ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_MAT_GetSound(MAT_BOUNCEMETAL_2, (es->eventParm&MATERIAL_MASK)), -1, -1 ); + } + break; + + case EV_DESTROY_GHOUL2_INSTANCE: + { + centity_t* cent2 = CG_GetEntity (es->eventParm); + DEBUGNAME("EV_DESTROY_GHOUL2_INSTANCE"); + if ( cent2->ghoul2 && trap_G2_HaveWeGhoul2Models( cent2->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(cent2->ghoul2)); + } + break; + } + + //================================================================= + + // + // missile impacts + // + + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( (es->eventParm >> MATERIAL_BITS), dir ); + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum, + (cent->currentState.eFlags & EF_ALT_FIRING)?ATTACK_ALTERNATE:ATTACK_NORMAL ); + if ( es->otherEntityNum != cg.snap->ps.clientNum ) + { + // Some missiles - e.g. thrown knives stick in players (for visual effect only). + CG_HandleStickyMissile(cent,es,dir,es->otherEntityNum); + } + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( (es->eventParm >> MATERIAL_BITS), dir ); + CG_MissileHitWall(es->weapon, position, dir, + (es->eventParm & MATERIAL_MASK), (cent->currentState.eFlags & EF_ALT_FIRING)?ATTACK_ALTERNATE:ATTACK_NORMAL ); + break; + + case EV_BULLET_HIT_WALL: + DEBUGNAME("EV_BULLET_HIT_WALL"); + + if ( !(cg_antiLag.integer && cg_impactPrediction.integer && es->otherEntityNum == cg.predictedPlayerState.clientNum ) ) + { + // eventParm contains the direction byte and the material id + ByteToDir( (es->eventParm >> MATERIAL_BITS), dir ); + + // time contains the weapon and attack of the shot + CG_Bullet( es->pos.trBase, es->otherEntityNum, (es->time&0xFF), + dir, ENTITYNUM_WORLD, (es->eventParm & MATERIAL_MASK), + ((es->time>>8)&0xFF) ); + } + + break; + + case EV_BULLET_HIT_FLESH: + + DEBUGNAME("EV_BULLET_HIT_FLESH"); + + // Play hit sounds for local player + if ( es->otherEntityNum2 == cg.snap->ps.clientNum ) + { + if ( cg.snap->ps.stats[STAT_ARMOR] ) + { + trap_S_StartLocalSound ( cgs.media.armorHitSound[rand()%2], CHAN_AUTO ); + } + else + { + trap_S_StartLocalSound ( cgs.media.fleshHitSound[rand()%2], CHAN_AUTO ); + } + } + +#ifdef _SOF2_FLESHIMPACTPREDICTION + if ( !(cg_antiLag.integer && cg_impactPrediction.integer >= 2 && es->otherEntityNum == cg.predictedPlayerState.clientNum ) ) +#endif + { + int fxtype = MATERIAL_FLESH; + + // eventParm contains the direction byte + ByteToDir( es->eventParm, dir ); + + if (cg_lockBlood.integer) + { + fxtype = MATERIAL_NONE; + } + + CG_Bullet( es->pos.trBase, es->otherEntityNum, (es->time&0xFF), dir, + es->otherEntityNum2, fxtype, + ((es->time>>8)&0xFF) ); + + CG_AddProcGore ( cent ); + } + + break; + + case EV_EXPLOSION_HIT_FLESH: + + DEBUGNAME("EV_EXPLOSION_HIT_FLESH"); + + CG_AddProcGore ( cent ); + break; + + case EV_PLAY_EFFECT: + DEBUGNAME("EV_PLAY_EFFECT"); + if (es->eventParm != -1) + { + trap_FX_PlayEffectID(es->eventParm, es->origin, es->angles, -1, -1 ); + } + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ], -1, -1 ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ), -1, -1 ); + } + break; + + case EV_GLOBAL_SOUND: + if ( cg_soundGlobal.integer ) + { + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) + { + trap_S_StartLocalSound ( cgs.gameSounds[ es->eventParm ], CHAN_AUTO ); + } + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartLocalSound ( CG_CustomSound( es->number, s ), CHAN_AUTO ); + } + } + break; + + case EV_ENTITY_SOUND: + DEBUGNAME("EV_ENTITY_SOUND"); + //somewhat of a hack - weapon is the caller entity's index, trickedentindex is the proper sound channel + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->weapon, 0, cgs.gameSounds[ es->eventParm ], -1, -1 ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->weapon, 0, CG_CustomSound( es->weapon, s ), -1, -1 ); + } + break; + + case EV_GLASS_SHATTER: + DEBUGNAME("EV_GLASS_SHATTER"); + CG_GlassShatter(es->number, es->pos.trBase, es->angles, es->origin); + break; + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + if ( cent->currentState.number != cg.snap->ps.clientNum ) + { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_PAIN_WATER: + { + static drownIndex = 0; + DEBUGNAME("EV_PAIN_WATER"); + trap_S_StartSound ( NULL, es->number, CHAN_VOICE, cgs.media.drownPainSound[(drownIndex++)%2], -1, -1 ); + break; + } + + case EV_GAME_OVER: + DEBUGNAME("EV_OBITUARY"); + CG_GameOver ( es ); + break; + + case EV_GOGGLES: + DEBUGNAME("EV_GOGGLES"); + + // Sound is handled elsewhere for local client + trap_S_StartSound ( NULL, es->number, CHAN_AUTO, es->eventParm?cgs.media.gogglesOnSound:cgs.media.gogglesOffSound, 120, -1 ); + break; + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + case EV_TESTLINE: + DEBUGNAME("EV_TESTLINE"); + CG_TestLine(es->origin, es->origin2, 0, es->weapon, 1); + break; + + case EV_BODY_QUEUE_COPY: + DEBUGNAME("EV_BODY_QUEUE_COPY"); + + // First byte of eventParm is the client Number + // Second byte of eventParm is the direction of the incoming shot + // Third and fourth byte of eventParm is the hit location + ByteToDir( (es->eventParm & 0xFF), dir ); + + CG_BodyQueueCopy(cent, es->otherEntityNum, (es->eventParm>>8), dir); + break; + + case EV_PROC_GORE: + DEBUGNAME("EV_PROC_GORE"); + CG_AddProcGore(cent); + break; + + case EV_BOTWAYPOINT: + DEBUGNAME("EV_BOTWAYPOINT"); + CG_TestLine(cent->lerpOrigin, es->angles, 30000, 0x0000ff, 3); + //Just render for 30 seconds because this waypoint might not be rendered again for quite some time. + break; + + case EV_GAMETYPE_RESTART: + CG_MapRestart ( qtrue ); + break; + + case EV_USE: + break; + + case EV_WEAPON_CALLBACK: + DEBUGNAME("EV_WEAPON_CALLBACK"); + if ( cent->currentState.number == cg.snap->ps.clientNum ) + { + CG_WeaponCallback ( &cg.predictedPlayerState, + &cg_entities[cg.predictedPlayerState.clientNum], + (es->eventParm&0xFF), // Weapon id + ((es->eventParm>>8)&0xFF), // Anim id + ((es->eventParm>>16)&0xFF), // Anim choice + ((es->eventParm>>24)&0xFF) ); // Callback step + } + break; + + default: + DEBUGNAME("UNKNOWN"); + Com_Error( ERR_FATAL, "Unknown event: %i", event ); + break; + } + +} + +/* +============== +CG_CheckEvents +============== +*/ +void CG_CheckEvents( centity_t *cent ) +{ + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) + { + // already fired + if ( cent->previousEvent ) + { + return; + } + + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) + { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } + else + { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) + { + return; + } + + cent->previousEvent = cent->currentState.event; + + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) + { + return; + } + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); +} + diff --git a/code/cgame/cg_gametype.c b/code/cgame/cg_gametype.c new file mode 100644 index 0000000..fea4ad8 --- /dev/null +++ b/code/cgame/cg_gametype.c @@ -0,0 +1,97 @@ +// Copyright (C) 2001-2002 Raven Software +// +// cg_gametype.c -- dynamic gametype handling + +#include "cg_local.h" + +/* +=============== +CG_ParseGametypeItems +=============== +*/ +qboolean CG_ParseGametypeItems ( TGPGroup itemsGroup ) +{ + TGPGroup itemGroup; + int itemCount; + char temp[MAX_QPATH]; + + // Handle NULL for convienience + if ( !itemsGroup ) + { + return qfalse; + } + + // Loop over all the items and add each + itemGroup = trap_GPG_GetSubGroups ( itemsGroup ); + itemCount = 0; + + while ( itemGroup ) + { + // Parse icon file + trap_GPG_FindPairValue ( itemGroup, "icon", "", temp ); + bg_itemlist[ MODELINDEX_GAMETYPE_ITEM + itemCount ].icon = (char *)trap_VM_LocalStringAlloc ( temp ); + + // Parse display name file + trap_GPG_FindPairValue ( itemGroup, "displayname", "", temp ); + bg_itemlist[ MODELINDEX_GAMETYPE_ITEM + itemCount ].pickup_name = (char *)trap_VM_LocalStringAlloc ( temp ); + + // Parse world model file + trap_GPG_FindPairValue ( itemGroup, "model", "", temp ); + bg_itemlist[ MODELINDEX_GAMETYPE_ITEM + itemCount ].world_model[0] = (char *)trap_VM_LocalStringAlloc ( temp ); + + // Parse bolt model file + trap_GPG_FindPairValue ( itemGroup, "boltmodel", "", temp ); + trap_G2API_InitGhoul2Model(&cg_items[MODELINDEX_GAMETYPE_ITEM+itemCount].boltModel, temp, 0 , 0, 0, 0, 0); + cg_items[MODELINDEX_GAMETYPE_ITEM+itemCount].radius[0] = 60; + + CG_RegisterItemVisuals ( MODELINDEX_GAMETYPE_ITEM+itemCount ); + + itemCount++; + + // Next sub group + itemGroup = trap_GPG_GetNext(itemGroup); + } + + return qtrue; +} + +/* +=============== +CG_ParseGametypeFile +=============== +*/ +qboolean CG_ParseGametypeFile ( void ) +{ + TGenericParser2 GP2; + TGPGroup topGroup; + TGPGroup gametypeGroup; + + // Open the given gametype file + GP2 = trap_GP_ParseFile ( (char*)cgs.gametypeData->script, qtrue, qfalse ); + if (!GP2) + { + return qfalse; + } + + // Grab the top group and the list of sub groups + topGroup = trap_GP_GetBaseParseGroup(GP2); + gametypeGroup = trap_GPG_FindSubGroup(topGroup, "gametype" ); + if ( !gametypeGroup ) + { + trap_GP_Delete(&GP2); + return qfalse; + } + + // Mainly interested in the items within the file + CG_ParseGametypeItems ( trap_GPG_FindSubGroup ( gametypeGroup, "items" ) ); + + // Free up the parser + trap_GP_Delete(&GP2); + + // Defaults + trap_Cvar_Set ( "ui_blueteamname", "Blue Team" ); + trap_Cvar_Set ( "ui_redteamname", "Red Team" ); + + return qtrue; +} + diff --git a/code/cgame/cg_gore.c b/code/cgame/cg_gore.c new file mode 100644 index 0000000..d47cd53 --- /dev/null +++ b/code/cgame/cg_gore.c @@ -0,0 +1,1454 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_gore.c -- handle client-side gore + +#include "cg_local.h" +#if !defined(GHOUL2_SHARED_H_INC) +#include "..\ghoul2\G2_gore_shared.h" +#endif + + +void CG_AddGore(int type, float size, vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + SSkinGoreData goreSkin; + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + goreSkin.growDuration = -1; // default expandy time + goreSkin.goreScaleStartFraction = 1.0; // default start scale + goreSkin.frontFaces = qtrue; // forever + goreSkin.backFaces = qtrue; // forever + goreSkin.lifeTime = 0; + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = size; + goreSkin.TSize = size; + goreSkin.theta = flrand(0,6.28); + goreSkin.shaderEnum = type; + + VectorSet ( goreSkin.scale, 1, 1, 1 ); + + VectorCopy ( hitdirection, goreSkin.rayDirection); + + VectorCopy ( hitloc, goreSkin.hitLocation ); + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2,&goreSkin); +} + +void CG_AddGrowGore(int type, float size, int growtime, float startfrac, vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + SSkinGoreData goreSkin; + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + goreSkin.frontFaces = qtrue; // forever + goreSkin.backFaces = qtrue; // forever + goreSkin.lifeTime = 0; + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = size; + goreSkin.TSize = size; + goreSkin.theta = flrand(0,6.28); + goreSkin.shaderEnum = type; + goreSkin.growDuration = growtime; // default expandy time + goreSkin.goreScaleStartFraction = startfrac; // default start scale + + VectorSet ( goreSkin.scale, 1, 1, 1 ); + + VectorCopy ( hitdirection, goreSkin.rayDirection); + + VectorCopy ( hitloc, goreSkin.hitLocation ); + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2,&goreSkin); +} + +void CG_AddSlashGore(int type, float angle, float ssize, float tsize, vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + SSkinGoreData goreSkin; + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + goreSkin.growDuration = -1; // default expandy time + goreSkin.goreScaleStartFraction = 1.0; // default start scale + goreSkin.frontFaces = qtrue; // forever + goreSkin.backFaces = qtrue; // forever + goreSkin.lifeTime = 0; + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = ssize; + goreSkin.TSize = tsize; + goreSkin.theta = angle; + goreSkin.shaderEnum = type; + + VectorSet ( goreSkin.scale, 1, 1, 1 ); + + VectorCopy ( hitdirection, goreSkin.rayDirection); + + VectorCopy ( hitloc, goreSkin.hitLocation ); + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2,&goreSkin); +} + +void CG_AddSlashGrowGore(int type, float angle, float ssize, float tsize, int growtime, float startfrac, + vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + SSkinGoreData goreSkin; + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + goreSkin.frontFaces = qtrue; // forever + goreSkin.backFaces = qtrue; // forever + goreSkin.lifeTime = 0; + goreSkin.baseModelOnly = qfalse; + + goreSkin.growDuration = growtime; // default expandy time + goreSkin.goreScaleStartFraction = startfrac; // default start scale + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = ssize; + goreSkin.TSize = tsize; + goreSkin.theta = angle; + goreSkin.shaderEnum = type; + + VectorSet ( goreSkin.scale, 1, 1, 1 ); + + VectorCopy ( hitdirection, goreSkin.rayDirection); + + VectorCopy ( hitloc, goreSkin.hitLocation ); + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2,&goreSkin); +} + + +void CG_AddTimedGore(int type, float size, int duration, vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + SSkinGoreData goreSkin; + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + goreSkin.growDuration = -1; // default expandy time + goreSkin.goreScaleStartFraction = 1.0; // default start scale + goreSkin.frontFaces = qtrue; // forever + goreSkin.backFaces = qtrue; // forever + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = size; + goreSkin.TSize = size; + goreSkin.theta = flrand(0,6.28); + goreSkin.shaderEnum = type; + goreSkin.lifeTime = duration; + + VectorSet ( goreSkin.scale, 1, 1, 1 ); + + VectorCopy ( hitdirection, goreSkin.rayDirection); + + VectorCopy ( hitloc, goreSkin.hitLocation ); + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2,&goreSkin); +} + + +void CG_DoGoreFromWeapon( int weaponnum, int attack, vec3_t hitloc, vec3_t hitdirection, + int entnum, vec3_t entposition, float entangle, void *ghoul2) +{ + float angle, size, size2; + + switch (weaponnum) + { + case WP_KNIFE: + if (attack==ATTACK_ALTERNATE) + { + CG_AddGore(PGORE_PUNCTURE, flrand(3.5, 4.0), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.0*1.4, 15000, 0.1, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + else + { + angle= (M_PI / 2 * (1+2*irand(0,1))) + flrand( .7, .7); + switch(irand(1,3)) + { + case 1: + size = flrand(2.8, 3.2); + size2 = flrand(1.8, 2.2); + CG_AddSlashGore(PGORE_KNIFESLASH, + angle, size, size2, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + case 2: + size = 8.0f*flrand(.8, 1.2); + size2 = 1.75f*flrand(.8, 1.2); + CG_AddSlashGore(PGORE_KNIFESLASH2, + angle, size, size2, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + default: + size = flrand(3.0f,4.0f); + size2 = flrand(0.5f,1.0f); + CG_AddSlashGore(PGORE_KNIFESLASH3, + angle, size, size2, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddSlashGrowGore(PGORE_KNIFE_SOAK, angle, size*1.2, size2*2.0, 15000, 0.1, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + } + } + break; + + // Smaller guns with pistol whip altfires + case WP_M1911A1_PISTOL: + case WP_USSOCOM_PISTOL: + if (attack==ATTACK_ALTERNATE) + { // Bonk on the head + CG_AddGore(PGORE_BLOODY_SPLOTCH2, flrand(5.25f, 7.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + + } + else + { + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 3.75f, 4.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.5*1.35, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + break; + + // Small guns + case WP_MICRO_UZI_SUBMACHINEGUN: + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 3.75f, 4.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.5f*1.35f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + + // Shotgun with whip altfire + case WP_M590_SHOTGUN: + if (attack==ATTACK_ALTERNATE) + { // Bond on de haid + CG_AddGore(PGORE_BLOODY_SPLOTCH2, flrand(7.75, 11.25), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + else + { + CG_AddGore(irand(PGORE_SHOTGUN, PGORE_SHOTGUNBIG), flrand( 8.25f, 11.25f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 11.25f*1.25f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>1) + { + CG_AddGore(PGORE_PELLETS, 8.25f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + } + break; + + // Medium guns + case WP_M3A1_SUBMACHINEGUN: + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + + // Shotguns + case WP_USAS_12_SHOTGUN: + CG_AddGore(irand(PGORE_SHOTGUN, PGORE_SHOTGUNBIG), flrand( 8.25f, 11.25f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 11.25f*1.25f, 15000, 0.1, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>1) + { + CG_AddGore(PGORE_PELLETS, 8.25f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + break; + + // Assault rifle with grenade altfire + case WP_M4_ASSAULT_RIFLE: + if (attack==ATTACK_ALTERNATE) + { + CG_AddGore(PGORE_SHRAPNEL, flrand( 14.0f, 17.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>1) + { + CG_AddGore(PGORE_PELLETS, 10.0, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + else + { + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + break; + + // Assault rifle with bayonet altfire + case WP_AK74_ASSAULT_RIFLE: + if (attack==ATTACK_ALTERNATE) + { + CG_AddGore(PGORE_PUNCTURE, flrand(3.5f, 4.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 4.0f*1.4f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + else + { + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 5.25f, 7.5f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 7.5f*1.3f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + } + break; + + // Large-caliber bullets + case WP_MSG90A1: + case WP_M60_MACHINEGUN: + CG_AddGore(irand(PGORE_BULLET_E, PGORE_BULLET_G), flrand( 6.0f, 9.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGrowGore(PGORE_KNIFE_SOAK, 9.0f*1.25f, 15000, 0.1f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + + // Explosions + case WP_MM1_GRENADE_LAUNCHER: + case WP_RPG7_LAUNCHER: + case WP_M67_GRENADE: + case WP_F1_GRENADE: + case WP_L2A2_GRENADE: + case WP_MDN11_GRENADE: + CG_AddGore(PGORE_SHRAPNEL, flrand( 14.0f, 17.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>1) + { + CG_AddGore(PGORE_PELLETS, 10.0f, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + + // Stun/char + case WP_M84_GRENADE: + case WP_M15_GRENADE: + case WP_SMOHG92_GRENADE: + CG_AddGore(PGORE_BURN, flrand( 14.0f, 18.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + break; + + // Fire + case WP_ANM14_GRENADE: + CG_AddTimedGore(PGORE_IMMOLATE, flrand( 18.0f, 22.0f), 4000, + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + if (cg_goreDetail.integer>0) + { + CG_AddGore(PGORE_BURN, flrand( 18.0f, 22.0f), + hitloc, hitdirection, entnum, entposition, entangle, ghoul2); + } + break; + } +} + + +void CG_PredictedProcGore ( int weaponnum, int attack, vec3_t start, vec3_t end, centity_t* cent ) +{ + vec3_t direction; + + // if no blood then dont add the proc gore + if ( cg_lockBlood.integer || !cent->ghoul2 || !cent->currentValid) + { + return; + } + + VectorSubtract ( end, start, direction); + VectorNormalize ( direction); + + CG_DoGoreFromWeapon(weaponnum, attack, // Weaponnum and altattack + end, direction, // hitloc and hitdirection + cent->currentState.number, // victim entity number + cent->lerpOrigin, cent->pe.ghoulLegsAngles[YAW], // entity position, entity yaw + cent->ghoul2); +} + +/* +====================== +CG_AddProcGore + +Adds procedural gore to the player specified in the given cent. The cent is a +temp ent containing all the information about the shot. +====================== +*/ +void CG_AddProcGore(centity_t *cent) +{ + centity_t* source; + attackType_t attack; + vec3_t direction; + + // Blood locked? + if ( cg_lockBlood.integer || !cent->currentState.time ) + { + return; + } + + // No procedural gore on this shot. + if ( cent->currentState.time & GORE_NONE ) + { + return; + } + + source = CG_GetEntity ( cent->currentState.otherEntityNum2 ); + if (!source->ghoul2 || !source->currentValid) + { + return; + } + + // Extract the direction of fire + ByteToDir( cent->currentState.eventParm, direction ); + attack = ((cent->currentState.time>>8)&0xFF); + + CG_UpdatePlayerModel ( source ); + + CG_DoGoreFromWeapon(cent->currentState.time&0xFF, attack, // Weaponnum and altattack + cent->lerpOrigin, direction, // hitloc and hitdirection + cent->currentState.otherEntityNum2, // victim entity number + cent->currentState.angles, (cent->currentState.time>>16)&0x7FFF, // entity position, entity yaw + source->ghoul2); +} + + + +#define MAX_GORE_POOL 20000 + +static char gorePool[MAX_GORE_POOL]; +static int gorePoolSize = 0; + +static char *AllocGorePool(int size) +{ + gorePoolSize = ((gorePoolSize + 0x00000003) & 0xfffffffc); + + if (gorePoolSize + size > MAX_GORE_POOL) + { + Com_Error( ERR_DROP, "AllocGorePool: buffer exceeded (%d > %d)", gorePoolSize + size, MAX_GORE_POOL); + return 0; + } + + gorePoolSize += size; + + return &gorePool[gorePoolSize-size]; +} + +static char *AllocMultiString(TGPValue field) +{ + TGPValue value; + int size = 1; + char name[256]; + char *output, *pos; + + if (!field) + { + return 0; + } + + value = trap_GPV_GetList(field); + while(value) + { + trap_GPV_GetName(value, name); + size += strlen(name) + 1; + value = trap_GPV_GetNext(value); + } + + output = pos = AllocGorePool(size); + value = trap_GPV_GetList(field); + while(value) + { + trap_GPV_GetName(value, name); + strcpy(pos, name); + pos += strlen(name) + 1; + value = trap_GPV_GetNext(value); + } + *pos = 0; + + return output; +} + + + + + + + + +#define GORE_CHILD 0x00000001 +#define GORE_NO_CHILD_SURFACES_ON 0x00000002 +#define GORE_NO_CHILD_FX 0x00000004 +#define GORE_NO_CHILD_CHUNKS 0x00000008 +#define GORE_NO_CHILD_BOLTONS 0x00000010 + +typedef enum +{ + GORE_SIDE_RIGHT = 0, + GORE_SIDE_LEFT, + GORE_SIDE_MAX +} EGoreSide; + +typedef struct SGoreInfo +{ + const char *mLongName; + const char *mShortName; +} TGoreInfo; + +typedef struct SGoreLocation +{ + const char *mPublicName; + EGoreSide mPrimarySide; + EGoreSide mOppositeSide; +} TGoreLocation; + +typedef struct SGoreEffectType +{ + char mName[MAX_QPATH]; + int mFXID; + + struct SGoreEffectType *mNext; +} TGoreEffectType; + +typedef struct SGorePieceType +{ + char mName[MAX_QPATH]; + char mBolt[MAX_QPATH]; + + TGhoul2 mG2Model; + qhandle_t mNormalModel; + + struct SGorePieceType *mNext; +} TGorePieceType; + +typedef struct SGoreEffect +{ + char mName[MAX_QPATH]; + char mBolt[MAX_QPATH]; + + struct SGoreEffect *mNext; +} TGoreEffect; + +typedef struct SGoreBoltOn +{ + char mName[MAX_QPATH]; + char mBolt[MAX_QPATH]; + + struct SGoreBoltOn *mNext; +} TGoreBoltOn; + +typedef struct SGoreChunk +{ + char mRoot[MAX_QPATH]; + char mBone[MAX_QPATH]; + char *mSurfacesOn; + char *mChildrenOff; + float mMinForce; + float mMaxForce; + + struct SGoreChunk *mNext; +} TGoreChunk; + +typedef struct SGoreArea +{ + char mLocation[MAX_QPATH]; + char *mSurfacesOff; + char *mSurfacesOn; + char *mBoltsOff; + char *mChildren; + unsigned mFlags; + + struct SGoreEffect *mFX; + struct SGoreBoltOn *mBoltOns; + struct SGoreChunk *mChunks; + + struct SGoreArea *mNext; +} TGoreArea; + + + + + + + + + + + +static TGoreEffectType *GoreEffectTypes; +static TGorePieceType *GorePieceTypes; +static TGoreArea *GoreAreas; +static TGoreInfo GoreInfo[GORE_SIDE_MAX] = +{ + { "right", "r" }, + { "left", "l" } +}; + +static TGoreLocation GoreLocations[] = +{ + { "none", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + + { "foot", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "foot", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + { "leg_upper", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "leg_upper", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + { "leg_lower", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "leg_lower", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + + { "hand", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "hand", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + { "arm_lower", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "arm_lower", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + + { "head", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "torso", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + + { "torso", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "torso", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + { "torso", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + //{ "torso", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + { "arm_upper", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + //{ "torso", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + { "arm_upper", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + + { "torso", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + + { "", GORE_SIDE_LEFT, GORE_SIDE_RIGHT }, + + // DEBUG ONE + { "torso", GORE_SIDE_RIGHT, GORE_SIDE_LEFT }, + +/* + "hand_right", + "arm_lower_right", + "torso_right", + "arm_upper_right", + "leg_lower_right", + "leg_upper_right", + "hip_right", + "head_back_lower_right", + "head_back_upper_right", + "head_front_lower_right", + "head_front_mid_right", + "head_front_upper_right", + "head_side_right", + "head_right", +*/ +}; + + + + + + + + + + + + + + +static const char *CreateFinalName(const char *Input, EGoreSide *Primary, EGoreSide *Opposite, qboolean SwapIfOpposite) +{ + static char output[256]; + char *outputPos; + const char *replace = ""; + EGoreSide use = GORE_SIDE_RIGHT; + qboolean doSwap = qfalse; + EGoreSide save; + const char *origInput = Input; + + outputPos = output; + while(*Input) + { + if ((*Input) == '<') + { + Input++; + if ((*Input) == 'P') + { // default to primary + use = *Primary; + } + else if ((*Input) == 'O') + { // use opposite + use = *Opposite; + doSwap = qtrue; + } + else + { + Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput); + break; + } + + Input++; + if ((*Input) == 'L') + { // default to long name + replace = GoreInfo[use].mLongName; + } + else if ((*Input) == 'S') + { // use short name + replace = GoreInfo[use].mShortName; + } + else + { + Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput); + break; + } + + strcpy(outputPos, replace); + outputPos += strlen(replace); + + Input++; + + if ((*Input) != '>') + { + Com_Error(ERR_DROP, "CreateFinalName: bad input string: %s", origInput); + break; + } + } + else + { + *outputPos++ = *Input; + } + + Input++; + } + + *outputPos = 0; + if (SwapIfOpposite && doSwap) + { + save = *Primary; + *Primary = *Opposite; + *Opposite = save; + } + + return output; +} + +static TGoreArea *FindGoreZone(const char *Location, EGoreSide Primary, EGoreSide Opposite) +{ + TGoreArea *gore = GoreAreas; + + while(gore) + { + if (Q_stricmp(CreateFinalName(gore->mLocation, &Primary, &Opposite, qfalse), Location) == 0) + { + return gore; + } + + gore = gore->mNext; + } + + return 0; +} + +static TGoreEffectType *FindGoreEffectType(const char *Name) +{ + TGoreEffectType *effect = GoreEffectTypes; + + while(effect) + { + if (Q_stricmp(effect->mName, Name) == 0) + { + return effect; + } + + effect = effect->mNext; + } + + return 0; +} + +static TGorePieceType *FindGorePieceType(const char *Name) +{ + TGorePieceType *piece = GorePieceTypes; + + while(piece) + { + if (Q_stricmp(piece->mName, Name) == 0) + { + return piece; + } + + piece = piece->mNext; + } + + return 0; +} + +static void CG_ParseGoreEffect(TGPGroup group) +{ + TGoreEffectType *effect; + char file[256]; + + effect = (TGoreEffectType *)AllocGorePool(sizeof(*effect)); + memset(effect, 0, sizeof(*effect)); + + effect->mNext = GoreEffectTypes; + GoreEffectTypes = effect; + + trap_GPG_FindPairValue(group, "Name", "", effect->mName); + trap_GPG_FindPairValue(group, "File", "", file); + effect->mFXID = trap_FX_RegisterEffect(file); +} + +static void CG_ParseGorePiece(TGPGroup group) +{ + TGorePieceType *piece; + char file[256]; + + piece = (TGorePieceType *)AllocGorePool(sizeof(*piece)); + memset(piece, 0, sizeof(*piece)); + + piece->mNext = GorePieceTypes; + GorePieceTypes = piece; + + trap_GPG_FindPairValue(group, "Name", "", piece->mName); + trap_GPG_FindPairValue(group, "Bolt", "", piece->mBolt); + trap_GPG_FindPairValue(group, "Model", "", file); + + if (trap_G2API_InitGhoul2Model(&piece->mG2Model, file, 0, 0, 0, 0, 0) == -1) + { // wasn't a g2 model, so try a regular one + piece->mNormalModel = trap_R_RegisterModel(file); + } +} + +static TGoreEffect *CG_ParseFX(TGPGroup group) +{ + TGoreEffect *effect; + + effect = (TGoreEffect *)AllocGorePool(sizeof(*effect)); + memset(effect, 0, sizeof(*effect)); + + trap_GPG_FindPairValue(group, "Name", "", effect->mName); + trap_GPG_FindPairValue(group, "Bolt", "", effect->mBolt); + + return effect; +} + +static TGoreBoltOn *CG_ParseBoltOn(TGPGroup group) +{ + TGoreBoltOn *bolt; + + bolt = (TGoreBoltOn *)AllocGorePool(sizeof(*bolt)); + memset(bolt, 0, sizeof(*bolt)); + + trap_GPG_FindPairValue(group, "Name", "", bolt->mName); + trap_GPG_FindPairValue(group, "Bolt", "", bolt->mBolt); + + return bolt; +} + +static TGoreChunk *CG_ParseChunk(TGPGroup group) +{ + TGoreChunk *chunk; + char temp[256]; + + chunk = (TGoreChunk *)AllocGorePool(sizeof(*chunk)); + memset(chunk, 0, sizeof(*chunk)); + + trap_GPG_FindPairValue(group, "Root", "", chunk->mRoot); + trap_GPG_FindPairValue(group, "Bone", "", chunk->mBone); + chunk->mSurfacesOn = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_On")); + chunk->mChildrenOff = AllocMultiString(trap_GPG_FindPair(group, "Children_Off")); + + trap_GPG_FindPairValue(group, "MinForce", "40", temp); + chunk->mMinForce = atof(temp); + trap_GPG_FindPairValue(group, "MaxForce", "80", temp); + chunk->mMaxForce = atof(temp); + + return chunk; +} + +static void CG_ParseGoreArea(TGPGroup group) +{ + TGoreArea *gore; + TGoreEffect *effect; + TGoreBoltOn *bolt; + TGoreChunk *chunk; + TGPValue flags, value; + TGPGroup sub; + char name[256]; + + gore = (TGoreArea *)AllocGorePool(sizeof(*gore)); + memset(gore, 0, sizeof(*gore)); + + gore->mNext = GoreAreas; + GoreAreas = gore; + + trap_GPG_FindPairValue(group, "Location", "", gore->mLocation); + + gore->mSurfacesOff = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_Off")); + gore->mSurfacesOn = AllocMultiString(trap_GPG_FindPair(group, "Surfaces_On")); + gore->mBoltsOff = AllocMultiString(trap_GPG_FindPair(group, "Bolts_Off")); + gore->mChildren = AllocMultiString(trap_GPG_FindPair(group, "Children")); + + flags = trap_GPG_FindPair(group, "Flags"); + if (flags) + { + value = trap_GPV_GetList(flags); + while(value) + { + trap_GPV_GetName(value, name); + if (Q_stricmp(name, "NoChildSurfacesOn") == 0) + { + gore->mFlags |= GORE_NO_CHILD_SURFACES_ON; + } + else if (Q_stricmp(name, "NoChildFX") == 0) + { + gore->mFlags |= GORE_NO_CHILD_FX; + } + else if (Q_stricmp(name, "NoChildChunks") == 0) + { + gore->mFlags |= GORE_NO_CHILD_CHUNKS; + } + else if (Q_stricmp(name, "NoChildBoltOns") == 0) + { + gore->mFlags |= GORE_NO_CHILD_BOLTONS; + } + + value = trap_GPV_GetNext(value); + } + } + + sub = trap_GPG_GetSubGroups(group); + while(sub) + { + trap_GPG_GetName(sub, name); + if (Q_stricmp(name, "FX") == 0) + { + effect = CG_ParseFX(sub); + effect->mNext = gore->mFX; + gore->mFX = effect; + } + else if (Q_stricmp(name, "Chunk") == 0) + { + chunk = CG_ParseChunk(sub); + chunk->mNext = gore->mChunks; + gore->mChunks = chunk; + } + else if (Q_stricmp(name, "BoltOn") == 0) + { + bolt = CG_ParseBoltOn(sub); + bolt->mNext = gore->mBoltOns; + gore->mBoltOns = bolt; + } + + sub = trap_GPG_GetNext(sub); + } +} + +qboolean CG_ParseGore(void) +{ + TGenericParser2 GP2; + TGPGroup topGroup, topSubs; + char name[256]; + + GP2 = trap_GP_ParseFile("ext_data/sof2.gore", qtrue, qfalse); + if (!GP2) + { + return qfalse; + } + + gorePoolSize = 0; + GoreEffectTypes = 0; + GorePieceTypes = 0; + GoreAreas = 0; + + topGroup = trap_GP_GetBaseParseGroup(GP2); + topSubs = trap_GPG_GetSubGroups(topGroup); + while(topSubs) + { + trap_GPG_GetName(topSubs, name); + if (Q_stricmp(name, "gore_area") == 0) + { + CG_ParseGoreArea(topSubs); + } + else if (Q_stricmp(name, "gore_effect") == 0) + { + CG_ParseGoreEffect(topSubs); + } + else if (Q_stricmp(name, "gore_piece") == 0) + { + CG_ParseGorePiece(topSubs); + } + + topSubs = trap_GPG_GetNext(topSubs); + } + + trap_GP_Delete(&GP2); + + return qtrue; +} + + + + + + + + + + + + + + + + + +static void CG_ProcessSurfaceList(void *model, char *surfaceList, int flags, + EGoreSide Primary, EGoreSide Opposite) +{ + while(surfaceList && surfaceList[0]) + { + if (!trap_G2API_SetSurfaceOnOff(model, 0, CreateFinalName(surfaceList, &Primary, &Opposite, qfalse), flags)) + { + +#ifdef _DEBUG +// Com_Printf("Missing surface '%s'\n", surfaceList); +#endif + } + surfaceList += strlen(surfaceList) + 1; + } +} + +static void CG_ProcessBoltList(void *model, char *boltList, EGoreSide Primary, EGoreSide Opposite) +{ + int numModels; + int boltIndex; + int i; + + while(boltList && boltList[0]) + { + numModels = trap_G2API_GetNumModels(model); + boltIndex = trap_G2API_FindBoltIndex(model, 0, CreateFinalName(boltList, &Primary, &Opposite, qfalse)); + if (boltIndex != -1) + { + for(i=1;imSurfacesOff, G2SURFACEFLAG_OFF, Primary, Opposite); + + child = gore->mChildren; + while(child && child[0]) + { + CG_ProcessChunkChild(Ghoul2, child, Primary, Opposite); + + child += strlen(child) + 1; + } +} + +static void CG_ProcessChunk(int clientNum, centity_t *cent, TGoreChunk *chunk, vec3_t Direction, + EGoreSide Primary, EGoreSide Opposite) +{ + localEntity_t *le; + refEntity_t *re; + int bolt; + char *child; + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + clientInfo_t *ci; + mdxaBone_t matrix; + qboolean boltMatrixOK = qfalse;; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_GIB; + le->startTime = cg.time; + le->endTime = le->startTime + BODY_SINK_DELAY + BODY_SINK_TIME; + le->leFlags = LEF_TUMBLE; + le->bounceFactor = 0.2f; + re->radius = 50; + re->renderfx = RF_MINLIGHT; + + VectorSet ( re->modelScale, 1, 1, 1 ); + + AxisCopy( axisDefault, re->axis ); + + le->pos.trType = TR_GRAVITY; + VectorMA ( vec3_origin, irand ( chunk->mMinForce * 2, chunk->mMaxForce * 2), Direction, le->pos.trDelta ); + le->pos.trDelta[2] = flrand ( 100, 150 ); + le->pos.trTime = cg.time; + + le->angles.trType = TR_LINEAR_STOP; + VectorClear(le->angles.trBase); + le->angles.trBase[YAW] = crandom() * 15; + le->angles.trDelta[0] = 0.0; // crandom(); + le->angles.trDelta[YAW] = crandom() * 15 - 7; + le->angles.trDelta[2] = 0.0; // crandom(); + le->angles.trDuration = BODY_SINK_DELAY + BODY_SINK_TIME; + le->angles.trTime = cg.time; + + le->zOffset = 26.0; + + // ghoul stuff to do limbs + if (!cent->ghoul2) + { + Com_Error(ERR_DROP, "CG_ProcessChunk invalid g2 pointer for client %d\n", clientNum); + } + trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &re->ghoul2); + trap_G2API_SetRootSurface(&re->ghoul2, 0, CreateFinalName(chunk->mRoot, &Primary, &Opposite, qfalse)); + if (!re->ghoul2) + { // whoa, that surface caused our model to go away??? + CG_FreeLocalEntity(le); + return; + } + + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(chunk->mBone, &Primary, &Opposite, qfalse)); + if (bolt != -1) + { + boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + } + + if (bolt == -1 || !boltMatrixOK) + { + return; + } + + matrix.matrix[0][3] = cent->lerpOrigin[0]; + matrix.matrix[1][3] = cent->lerpOrigin[1]; + matrix.matrix[2][3] = cent->lerpOrigin[2]; + + le->pos.trBase[0] = re->origin[0] = matrix.matrix[0][3]; + le->pos.trBase[1] = re->origin[1] = matrix.matrix[1][3]; + le->pos.trBase[2] = re->origin[2] = matrix.matrix[2][3]; + + bolt = trap_G2API_AddBolt(re->ghoul2, 0, CreateFinalName(chunk->mBone, &Primary, &Opposite, qfalse)); + trap_G2API_SetNewOrigin(re->ghoul2, 0, bolt); + + ci = &cgs.clientinfo[clientNum]; + anim = &ci->animations[cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT]; + animSpeed = 50.0f / anim->frameLerp; +// trap_G2API_SetBoneAnim(re->ghoul2, 0, "model_root", 0, 0, flags, animSpeed, cg.time, -1, 0); +// trap_G2API_SetBoneAnim(re->ghoul2, 0, "lower_lumbar", 0, 0, flags, animSpeed, cg.time, -1, 0); + trap_G2API_SetBoneAnim(re->ghoul2, 0, "model_root", anim->firstFrame + anim->numFrames - 1, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 0); + trap_G2API_SetBoneAnim(re->ghoul2, 0, "lower_lumbar", anim->firstFrame + anim->numFrames - 1, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, -1, 0); + + CG_ProcessSurfaceList(re->ghoul2, chunk->mSurfacesOn, 0, Primary, Opposite); + + child = chunk->mChildrenOff; + while(child && child[0]) + { + CG_ProcessChunkChild(re->ghoul2, child, Primary, Opposite); + + child += strlen(child) + 1; + } +} + +static void CG_ProcessBoltOn(int clientNum, centity_t *cent, TGoreBoltOn *bolton, + EGoreSide Primary, EGoreSide Opposite) +{ + TGorePieceType *piece = FindGorePieceType(bolton->mName); + const char *boltPosition; + int pieceIndex; + int boltIndex; + + if (!piece) + { + return; + } + + if (piece->mG2Model) + { + boltPosition = bolton->mBolt; + if (!boltPosition[0]) + { + boltPosition = piece->mBolt; + } + + pieceIndex = trap_G2API_CopySpecificGhoul2Model(piece->mG2Model, 0, cent->ghoul2, -1); + if (pieceIndex != -1) + { + boltIndex = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(boltPosition, &Primary, &Opposite, qfalse)); + if (boltIndex != -1) + { + trap_G2API_AttachG2Model(cent->ghoul2, pieceIndex, cent->ghoul2, boltIndex, 0); + } + } + } +} + +static void CG_ProcessGore(int clientNum, centity_t *cent, const char *Location, unsigned Flags, + vec3_t Direction, EGoreSide Primary, EGoreSide Opposite) +{ + TGoreArea *gore; + TGoreEffect *fx; + TGoreChunk *chunk; + TGoreBoltOn *bolton; + char *children; + int bolt; + mdxaBone_t matrix; + qboolean boltMatrixOK; + vec3_t origin; + TGoreEffectType *effect; + char finalName[256]; + + strcpy(finalName, CreateFinalName(Location, &Primary, &Opposite, qtrue)); + gore = FindGoreZone(finalName, Primary, Opposite); + if (!gore) + { + return; + } + + if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_FX) ) + { + fx = gore->mFX; + while(fx) + { + effect = FindGoreEffectType(fx->mName); + if (effect) + { + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, CreateFinalName(fx->mBolt, &Primary, &Opposite, qfalse)); + if (bolt != -1) + { + vec3_t axis[3]; + int boltInfo; + + boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + axis[0][0] = matrix.matrix[0][0]; + axis[0][1] = matrix.matrix[1][0]; + axis[0][2] = matrix.matrix[2][0]; + axis[1][0] = matrix.matrix[0][1]; + axis[1][1] = matrix.matrix[1][1]; + axis[1][2] = matrix.matrix[2][1]; + axis[2][0] = matrix.matrix[0][2]; + axis[2][1] = matrix.matrix[1][2]; + axis[2][2] = matrix.matrix[2][2]; + origin[0] = matrix.matrix[0][3]; + origin[1] = matrix.matrix[1][3]; + origin[2] = matrix.matrix[2][3]; + + + boltInfo = (( 0/*modelnum*/ & MODEL_AND ) << MODEL_SHIFT ); + boltInfo |= (( bolt & BOLT_AND ) << BOLT_SHIFT ); + boltInfo |= (( cent->currentState.number & ENTITY_AND ) << ENTITY_SHIFT ); + + trap_FX_PlayEntityEffectID( effect->mFXID, origin, axis, boltInfo, -1, -1, -1); +/* boltMatrixOK = trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->lerpAngles, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + if (boltMatrixOK) + { + origin[0] = matrix.matrix[0][3]; + origin[1] = matrix.matrix[1][3]; + origin[2] = matrix.matrix[2][3]; + trap_FX_PlayEffectID(effect->mFXID, origin, Direction, -1, -1 ); + } +*/ } + } + + fx = fx->mNext; + } + } + + if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_CHUNKS) ) + { + chunk = gore->mChunks; + while(chunk) + { + CG_ProcessChunk(clientNum, cent, chunk, Direction, Primary, Opposite); + + chunk = chunk->mNext; + } + } + + if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_BOLTONS) ) + { + bolton = gore->mBoltOns; + while(bolton) + { + CG_ProcessBoltOn(clientNum, cent, bolton, Primary, Opposite); + + bolton = bolton->mNext; + } + } + + CG_ProcessSurfaceList(cent->ghoul2, gore->mSurfacesOff, G2SURFACEFLAG_OFF, Primary, Opposite); + if (!(Flags & GORE_CHILD) || !(Flags & GORE_NO_CHILD_SURFACES_ON) ) + { // children with no gore from the parents shouldn't do this + CG_ProcessSurfaceList(cent->ghoul2, gore->mSurfacesOn, 0, Primary, Opposite); + } + CG_ProcessBoltList(cent->ghoul2, gore->mBoltsOff, Primary, Opposite); + + children = gore->mChildren; + while(children && children[0]) + { + CG_ProcessGore(clientNum, cent, children, gore->mFlags | Flags | GORE_CHILD, Direction, Primary, Opposite); + children += strlen(children) + 1; + } +} + + +void CG_ApplyGore(int clientNum, centity_t *cent, int hitLocation, vec3_t Direction) +{ + TGoreLocation *Location = 0; + const char *dg, *token; + char area[256]; + EGoreSide side; + int i; + + // Gore locked? + if ( cg_lockSever.integer ) + { + return; + } + + dg = cg_DebugGore.string; + if (dg[0]) + { + strcpy(area, COM_Parse(&dg)); + token = COM_Parse(&dg); + for(side=0;side= (sizeof(GoreLocations) / sizeof(struct SGoreLocation))) + { + Com_Error( ERR_DROP, "CG_ApplyGore: invalid hit location %d\n", hitLocation); + return; + } + Location = &GoreLocations[hitLocation]; + } + else + { + // just so that I don't have to keep restarting the game to change the file + CG_ParseGore(); + + Com_Printf ( "GORE: Applying gore to location '%s'\n", Location->mPublicName ); + } + + CG_ProcessGore(clientNum, cent, Location->mPublicName, 0, Direction, Location->mPrimarySide, Location->mOppositeSide); +} + +void CG_ShutdownGore(void) +{ + TGorePieceType *piece = GorePieceTypes; + + while(piece) + { + if (piece->mG2Model) + { + trap_G2API_CleanGhoul2Models(&piece->mG2Model); + } + piece = piece->mNext; + } + +} + + + diff --git a/code/cgame/cg_info.c b/code/cgame/cg_info.c new file mode 100644 index 0000000..e8e0224 --- /dev/null +++ b/code/cgame/cg_info.c @@ -0,0 +1,318 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +static int loadingPlayerIconCount; +static int loadingItemIconCount; +static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; +static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; + +void CG_LoadBar(void); + +/* +====================== +CG_LoadingStage +====================== +*/ +void CG_LoadingStage ( int stage ) +{ + if ( !cg.loading ) + { + return; + } + + cg.loadStage = stage; + + if ( cg.loadStage ) + { + trap_UpdateScreen(); + } +} + +/* +====================== +CG_LoadingString +====================== +*/ +void CG_LoadingString( const char *s ) +{ + if ( !cg.loading ) + { + return; + } + + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) +{ + gitem_t *item; + + if ( !cg.loading ) + { + return; + } + + item = &bg_itemlist[itemNum]; + + if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { + loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) +{ + const char *info; + char personality[MAX_QPATH]; + + if ( !cg.loading ) + { + return; + } + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawInformation( void ) +{ + static qhandle_t levelshot = 0; + static char levelshotShader[MAX_QPATH] = ""; + + const char *s; + const char *info; + const char *sysInfo; + char shader[MAX_QPATH]; + int y; + int value; + qhandle_t overlay; + char buf[1024]; + + // As long as the map change flag is set we draw the map change screen + if ( cg.mMapChange ) + { + CG_DrawMapChange ( ); + return; + } + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + if ( cg.mInRMG ) + { + const char* terrainInfo; + + terrainInfo = CG_ConfigString( CS_TERRAINS + 1 ); + if ( terrainInfo ) + { + s = Info_ValueForKey ( terrainInfo, "terraindef" ); + Com_sprintf ( shader, sizeof(shader), "gfx/menus/levelshots/mp_rmg_%s", s ); + } + else + { + Com_sprintf ( shader, sizeof(shader), "gfx/menus/levelshots/unknownmap_mp" ); + } + } + else + { + s = Info_ValueForKey( info, "mapname" ); + Com_sprintf ( shader, sizeof(shader), "gfx/menus/levelshots/%s", s ); + } + + if ( Q_stricmp ( levelshotShader, shader ) ) + { + levelshot = trap_R_RegisterShaderNoMip( shader ); + Com_sprintf ( levelshotShader, sizeof(levelshotShader), shader ); + } + + overlay = trap_R_RegisterShaderNoMip( "gfx/menus/levelshots/unknownmap_mp" ); + + // Draw the level shot + trap_R_SetColor( NULL ); + + if ( levelshot ) + { + vec4_t fade; + + fade[0] = 1.0f; + fade[1] = 1.0f; + fade[2] = 1.0f; + fade[3] = 1.0f - ((float)cg.loadStage / 15.0f); + + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + trap_R_SetColor ( fade ); + + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, overlay ); + + trap_R_SetColor ( NULL ); + } + else + { + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, overlay ); + } + + // Draw the progress bar + CG_LoadBar(); + + // Determine the string to print + if ( cg.infoScreenText[0] ) + { + s = va("Loading... %s", cg.infoScreenText); + } + else + { + s = "Awaiting snapshot..."; + } + + // Render the string + CG_DrawText( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, 198 - 32, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + // draw info string information + + y = 250-32; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); + if ( !atoi( buf ) ) + { + // server hostname + Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); + Q_CleanStr(buf); + + CG_DrawText ( 320 - trap_R_GetTextWidth ( buf, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, buf, 0, 0 ); + + y += PROP_HEIGHT; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( atoi(s) ) + { + s = "Pure Server"; + CG_DrawText( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + y += PROP_HEIGHT; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) + { + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) + { + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) + { + s = "CHEATS ARE ENABLED"; + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + + y += PROP_HEIGHT; + + cg.cheats = qtrue; + } + else + cg.cheats = qfalse; + + s = cgs.gametypeData->displayName; + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + y += PROP_HEIGHT; + + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) + { + s = va( "Timelimit %i", value ); + CG_DrawText ( 320 - trap_R_GetTextWidth( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + y += PROP_HEIGHT; + } + + value = atoi( Info_ValueForKey( info, "scorelimit" ) ); + if ( value ) + { + s = va( "Scorelimit %i", value ); + CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 ) / 2, y, + cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 ); + y += PROP_HEIGHT; + } +} + +/* +=================== +CG_LoadBar +=================== +*/ + +#define LOADBAR_CLIP_WIDTH 256 +#define LOADBAR_CLIP_HEIGHT 64 +#define LOADBAR_BULLET_WIDTH 16 +#define LOADBAR_BULLET_HEIGHT 64 + +void CG_LoadBar(void) +{ + int x,y,i; + + y = 50; + x = (640 - LOADBAR_CLIP_WIDTH) / 2; + + for (i=0;i < cg.loadStage; i++ ) + { + CG_DrawPic(x + (i*LOADBAR_BULLET_WIDTH), y, LOADBAR_BULLET_WIDTH, LOADBAR_BULLET_HEIGHT, cgs.media.loadBulletShader ); + } + + CG_DrawPic ( x, y, LOADBAR_CLIP_WIDTH, LOADBAR_CLIP_HEIGHT, cgs.media.loadClipShader ); +} diff --git a/code/cgame/cg_light.c b/code/cgame/cg_light.c new file mode 100644 index 0000000..7adcbe9 --- /dev/null +++ b/code/cgame/cg_light.c @@ -0,0 +1,89 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_light.c + +#include "cg_local.h" + +#if !defined(CG_LIGHTS_H_INC) + #include "cg_lights.h" +#endif + +static clightstyle_t cl_lightstyle[MAX_LIGHT_STYLES]; +static int lastofs; + +/* +================ +FX_ClearLightStyles +================ +*/ +void CG_ClearLightStyles (void) +{ + int i; + + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + lastofs = -1; + + for(i=0;ilength) + { + ls->value[0] = ls->value[1] = ls->value[2] = ls->value[3] = 255; + } + else if (ls->length == 1) + { + ls->value[0] = ls->map[0][0]; + ls->value[1] = ls->map[0][1]; + ls->value[2] = ls->map[0][2]; + ls->value[3] = 255; //ls->map[0][3]; + } + else + { + ls->value[0] = ls->map[ofs%ls->length][0]; + ls->value[1] = ls->map[ofs%ls->length][1]; + ls->value[2] = ls->map[ofs%ls->length][2]; + ls->value[3] = 255; //ls->map[ofs%ls->length][3]; + } + trap_R_SetLightStyle(i, *(int*)ls->value); + } +} + +void CG_SetLightstyle (int i) +{ + const char *s; + int j, k; + + s = CG_ConfigString( i+CS_LIGHT_STYLES ); + j = strlen (s); + if (j >= MAX_QPATH) + { + Com_Error (ERR_DROP, "svc_lightstyle length=%i", j); + } + + cl_lightstyle[(i/3)].length = j; + for (k=0 ; kserverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + qboolean needNextSnap; +// snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean mMapChange; // true if map is changing + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + qboolean mapRestart; // set on a map restart to set back the weapon + qboolean gametypeStarted; // has the gametype started yet? + qboolean mInRMG; + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + int deathTime; // time of death + + // input state sent to server + int weaponSelect; + int weaponLastSelect; + int weaponOldSelect; + int weaponMenuSelect; + + qboolean weaponMenuUp; // weapon menu shown + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + + // zoom key + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int teamScores[2]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; // Whether or not the scoreboard is showing + float scoreBoardBottom; // Bottom coordinate of the scoreboard + char scoreBoardSpectators[1024]; // spectators string + int scoreFadeTime; + qboolean showAutomap; + char killerName[MAX_NAME_LENGTH]; + int spectatorTime; // next time to offset + + // centerprinting + int centerPrintTime; + char centerPrint[1024]; + int centerPrintLines; + float centerPrintScale; + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + int crosshairColorClientNum; + + vec4_t crosshairRGBA; + vec4_t crosshairFriendRGBA; + + // attacking player + int attackerTime; + int voiceTime; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[MAX_SOUNDBUFFER]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX; + float damageY; + + // view movement + float v_dmg_pitch; + float v_dmg_roll; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + int loadStage; + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + int testModel; + + // had to be moved so we wouldn't wipe these out with the memset + snapshot_t activeSnapshots[3]; + int activeSnapshot; + + TAnimInfoWeapon* viewWeaponAnim[5]; + + int weaponHideModels; + + CFxBoltInterface flashBoltInterface; + + qboolean cheats; + + qboolean popupObjectives; + + float shakeIntensity; + int shakeDuration; + int shakeStart; + + char sharedBuffer[MAX_CG_SHARED_BUFFER_SIZE]; + + centity_t* radarEntities[MAX_GAMETYPE_ITEMS+MAX_CLIENTS]; + int radarEntityCount; + + int flashbangTime; + int flashbangFadeTime; + float flashbangAlpha; + + animation_t hitAnimations[MAX_ANIMATIONS]; + TGhoul2 hitModel; + +} cg_t; + +extern centity_t cg_entities[MAX_GENTITIES]; +extern centity_t *cg_permanents[MAX_GENTITIES]; +extern int cg_numpermanents; + + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t +typedef struct +{ + qhandle_t whiteShader; + + qhandle_t armorShader; + + qhandle_t blueFriendShader; + qhandle_t redFriendShader; + qhandle_t deadShader; + + qhandle_t radarShader; + + qhandle_t botSmallShader; + + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t disconnectShader; + qhandle_t backTileShader; + + qhandle_t loadClipShader; + qhandle_t loadBulletShader; + + qhandle_t smokePuffShader; + qhandle_t waterBubbleShader; + + qhandle_t shadowMarkShader; + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t bloodMarkShader; + qhandle_t burnMarkShader; + + // scoreboard headers + qhandle_t scoreboard; + qhandle_t scoreboardHeader; + qhandle_t scoreboardLine; + qhandle_t scoreboardFooter; + qhandle_t scoreboardTotals; + + // sounds + sfxHandle_t talkSound; + + sfxHandle_t waterLeave; + sfxHandle_t waterJumpIn; + sfxHandle_t waterFootstep[2]; + sfxHandle_t waterWade[2]; + + sfxHandle_t armorHitSound[2]; + sfxHandle_t fleshHitSound[2]; + + qhandle_t cursor; + + // Fonts + qhandle_t lcdFont; + qhandle_t hudFont; + + qhandle_t glassBreakSound; + qhandle_t respawnSound; + qhandle_t itemRespawnSound; + qhandle_t fragSound; + qhandle_t fragSelfSound; + + qhandle_t goSound; + + qhandle_t zoomSound; + qhandle_t gogglesOnSound; + qhandle_t gogglesOffSound; + + qhandle_t flybySounds[NUMFLYBYS]; + + qhandle_t drownPainSound[2]; + qhandle_t drownDeathSound; + + qhandle_t damageDirShader; + qhandle_t playerFleshImpactEffect; + qhandle_t glassChunkEffect; + + qhandle_t mAutomap; + qhandle_t mAutomapPlayerIcon; + + TGhoul2 nightVisionModel; + + qhandle_t mBloodSmall; + + qhandle_t test; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a map_restart is done +typedef struct +{ + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int gameID; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum;// the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + int gametype; + gametypeData_t* gametypeData; + int dmflags; + int teamflags; + int scorelimit; + int timelimit; + int maxclients; + qboolean friendlyFire; + char mapname[MAX_QPATH]; + char gameover[MAX_QPATH]; + + qboolean pickupsDisabled; + + int voteTime; + int voteDuration; + int voteYes; + int voteNeeded; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int levelStartTime; + int gametypeTimerTime; + int gametypeMessageTime; + char gametypeMessage[MAX_STRING_TOKENS]; + + int scores1; + int scores2; + + // locally derived information from gamestate + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + qhandle_t gameIcons[MAX_ICONS]; + qhandle_t skins[MAX_CHARSKINS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char chatText[CHAT_HEIGHT][CHAT_WIDTH*3+1]; + int chatTime[CHAT_HEIGHT]; + int chatPos; + int chatLastPos; + + int cursorX; + int cursorY; + qboolean eventHandling; + void* capturedItem; + qhandle_t activeCursor; + + // media + cgMedia_t media; + + vec3_t mWorldMins; + vec3_t mWorldMaxs; + float mIRDist; + float mIRSeeThrough; + +} cgs_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; + +extern vmCvar_t con_notifyTime; +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_centerY; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawRadar; +extern vmCvar_t cg_drawTeamScores; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairRGBA; +extern vmCvar_t cg_crosshairFriendRGBA; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_shellEjection; + +extern vmCvar_t cg_goreDetail; + +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonYaw; +extern vmCvar_t cg_thirdPersonPitch; + +extern vmCvar_t cg_thirdPersonHorzOffset; + +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_chatTime; +extern vmCvar_t cg_chatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_antiLag; +extern vmCvar_t cg_impactPrediction; +extern vmCvar_t cg_autoReload; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_noProjectileTrail; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; + +extern vmCvar_t cg_DebugGore; + +extern vmCvar_t cg_lockSever; +extern vmCvar_t cg_lockBlood; +extern vmCvar_t cg_lockDeaths; + +extern vmCvar_t RMG_distancecull; +extern vmCvar_t cg_damageindicator; +extern vmCvar_t cg_tracerChance; + +extern vmCvar_t cg_animBlend; + +extern vmCvar_t cg_automap_x; +extern vmCvar_t cg_automap_y; +extern vmCvar_t cg_automap_w; +extern vmCvar_t cg_automap_h; +extern vmCvar_t cg_automap_a; + +extern vmCvar_t ui_info_redcount; +extern vmCvar_t ui_info_bluecount; +extern vmCvar_t ui_info_speccount; +extern vmCvar_t ui_info_freecount; +extern vmCvar_t ui_info_pickupsdisabled; +extern vmCvar_t ui_info_seenobjectives; + +extern vmCvar_t cg_voiceRadio; +extern vmCvar_t cg_voiceGlobal; +extern vmCvar_t cg_soundGlobal; +extern vmCvar_t cg_soundFrag; +extern vmCvar_t cg_weaponMenuFast; + +extern vmCvar_t cg_bodyTime; + +extern vmCvar_t rw_enabled; + +// +// cg_main.c +// +const char *CG_ConfigString ( int index ); +const char *CG_Argv ( int arg ); +centity_t* CG_GetEntity ( int index ); +void CG_StartMusic ( qboolean bForceStart ); +void CG_UpdateCvars ( void ); +void CG_UpdateTeamCountCvars ( void ); +int CG_CrosshairPlayer ( void ); +int CG_LastAttacker ( void ); +void CG_LoadMenus ( const char *menuFile); +void CG_KeyEvent ( int key, qboolean down); +void CG_MouseEvent ( int x, int y); +void CG_EventHandling ( int type); + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_AddBufferedSound ( sfxHandle_t sfx); +void CG_DrawActiveFrame ( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); +void CG_CameraShake ( float* origin, float intensity, int radius, int time ); +void CG_UpdateCameraShake ( vec3_t origin, vec3_t angles ); + +// +// cg_drawtools.c +// +void CG_AdjustFrom640 ( float *x, float *y, float *w, float *h ); +void CG_FillRect ( float x, float y, float width, float height, const float *color ); +void CG_DrawPic ( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawStretchPic ( float x, float y, float width, float height, float sx, float sy, float sw, float sh, const float* color, qhandle_t hShader ); +void CG_DrawRotatePic ( float x, float y, float width, float height,float angle, qhandle_t hShader ); +void CG_DrawRotatePic2 ( float x, float y, float width, float height,float angle, qhandle_t hShader ); +int CG_DrawStrlen ( const char *str ); +float* CG_FadeColor ( int startMsec, int totalMsec ); +float* CG_TeamColor ( int team ); +void CG_TileClear ( void ); +void CG_DrawRect ( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides ( float x, float y, float w, float h, float size); +void CG_DrawTopBottom ( float x, float y, float w, float h, float size); +void CG_GetColorForHealth ( vec4_t color, int health, int armor ); +void CG_DrawText ( float x, float y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags ); +void CG_DrawTextWithCursor ( float x, float y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags, int cursorPos, char cursor ); +void CG_DrawTimer ( float x, float y, qhandle_t font, float scale, vec4_t color, int flags, int msec ); + +// +// cg_draw.c, cg_newDraw.c +// +void CG_AddLagometerFrameInfo ( void ); +void CG_AddLagometerSnapshotInfo ( snapshot_t *snap ); +void CG_CenterPrint ( const char *str, float scale ); +void CG_DrawActive ( stereoFrame_t stereoView ); +void CG_OwnerDraw ( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, qhandle_t font, float scale, vec4_t color, qhandle_t shader, int textStyle, const char* param ); +float CG_GetValue ( int ownerDraw ); +qboolean CG_OwnerDrawVisible ( int flags, const char* param ); +qboolean CG_OwnerDrawDisabled ( int flags, const char* param ); +void CG_RunMenuScript ( const char **args); +void CG_GetTeamColor ( vec4_t *color); +const char* CG_GetGameStatusText ( void ); +void CG_Draw3DModel ( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Draw3DG2Model ( float x, float y, float w, float h, void *ghoul2, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar ( float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader ); +const char* CG_GameTypeString ( void ); +void CG_DrawMapChange ( void ); + +// +// cg_player.c +// +void CG_Player ( centity_t *cent ); +void CG_ResetPlayerEntity ( centity_t *cent ); +void CG_NewClientInfo ( int clientNum ); +sfxHandle_t CG_CustomSound ( int clientNum, const char *soundName); +sfxHandle_t CG_CustomPlayerSound ( int clientNum, ECustomSounds sound); +void CG_UpdatePlayerModel ( centity_t* cent); +TGhoul2 CG_RegisterIdentity ( TIdentity* identity, char *animationFile, gender_t* gender ); +void CG_RemoveIdentityItemsOnBack ( centity_t* cent ); +qboolean CG_PlayerShadow ( centity_t *cent, float *shadowPlane ); + +// +// cg_predict.c +// +void CG_BuildSolidList ( void ); +int CG_PointContents ( const vec3_t point, int passEntityNum ); +void CG_Trace ( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask ); +void CG_PlayerTrace ( trace_t* tr, const vec3_t start, const vec3_t end, int skipNumber ); +void CG_PredictPlayerState ( void ); +void CG_LoadDeferredPlayers ( void ); +void CG_InitHitModel ( void ); + +// +// cg_events.c +// +void CG_CheckEvents ( centity_t *cent ); +const char* CG_PlaceString ( int rank ); +void CG_EntityEvent ( centity_t *cent, vec3_t position ); +void CG_PainEvent ( centity_t *cent, int health ); + +// +// cg_ents.c +// +float* CG_SetEntitySoundPosition ( centity_t *cent ); +void CG_AddPacketEntities ( void ); +void CG_Beam ( centity_t *cent ); +void CG_AdjustPositionForMover ( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); +void CG_ScaleModelAxis ( refEntity_t* ent ); + +// +// cg_weapons.c +// +void CG_NextWeapon ( qboolean allowEmpty, int exclude ); +void CG_PrevWeapon ( qboolean allowEmpty, int exclude ); +void CG_Weapon_f ( void ); +qboolean CG_WeaponSelectable ( int i, qboolean allowEmpty ); +void CG_RegisterWeapon ( int weaponNum); +void CG_ShutDownWeapons ( void); +void CG_RegisterItemVisuals ( int itemNum ); +void CG_UpdateViewWeaponSurfaces ( playerState_t* ps ); +void CG_SetWeaponAnim ( int weaponAnim, playerState_t *ps ); +void CG_FireWeapon ( centity_t *cent, attackType_t attack ); +void CG_ProjectileThink ( centity_t *cent, int weaponNum ); +void CG_MissileHitWall ( int weapon, vec3_t origin, vec3_t dir, int material, attackType_t attack ); +void CG_MissileHitPlayer ( int weapon, vec3_t origin, vec3_t dir, int entityNum, attackType_t attack ); +void CG_HandleStickyMissile ( centity_t *cent,entityState_t *es,vec3_t dir,int targetEnt); +void CG_Bullet ( vec3_t end, int sourceEntityNum, int weapon, vec3_t normal, int fleshEntityNum, int material, attackType_t attack ); +void CG_AnimateViewWeapon ( playerState_t *ps ); +void CG_AddViewWeapon ( playerState_t *ps ); +void CG_PlayerWeaponEffects ( refEntity_t *parent, centity_t *cent, int team, vec3_t newAngles ); +void CG_DrawWeaponSelect ( void ); +void CG_OutOfAmmoChange ( int lastWeapon ); +void CG_WeaponCallback ( playerState_t* ps, centity_t* cent, int weapon, int anim, int animChoice, int step ); + +// +// cg_localents.c +// +void CG_InitLocalEntities ( void ); +void CG_FreeLocalEntity ( localEntity_t *le ); +localEntity_t* CG_AllocLocalEntity ( void ); +void CG_AddLocalEntities ( void ); + +// +// cg_effects.c +// +void CG_BubbleTrail ( vec3_t start, vec3_t end, float spacing ); +void CG_BulletFlyBySound ( vec3_t start, vec3_t end ); +void CG_GlassShatter ( int entnum, vec3_t org, vec3_t mins, vec3_t maxs); +void CG_TestLine ( vec3_t start, vec3_t end, int time, unsigned int color, int radius); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots ( void ); + +// +// cg_info.c +// +void CG_LoadingString ( const char *s ); +void CG_LoadingStage ( int stage ); +void CG_LoadingItem ( int itemNum ); +void CG_LoadingClient ( int clientNum ); +void CG_DrawInformation ( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawScoreboard ( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand ( void ); +void CG_InitConsoleCommands ( void ); + +// +// cg_servercmds.c +// +void CG_MapRestart ( qboolean gametypeRestart ); +void CG_ExecuteNewServerCommands ( int latestSequence ); +void CG_ParseServerinfo ( void ); +void CG_SetConfigValues ( void ); +void CG_LoadVoiceChats ( void ); +void CG_ShaderStateChanged ( void ); +void CG_VoiceChatLocal ( qboolean voiceOnly, int clientNum, const char* chatprefix, const char *cmd ); +void CG_PlayBufferedVoiceChats ( void ); + +// +// cg_playerstate.c +// +void CG_Respawn ( void ); +void CG_TransitionPlayerState ( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents ( playerState_t *ps ); + +// +// cg_playeranim.c +// +qboolean CG_ParseAnimationFile ( const char *filename, clientInfo_t *ci) ; + +// +// cg_gore.c +// +qboolean CG_ParseGore ( void ); +void CG_ApplyGore ( int clientNum, centity_t *cent, int hitLocation, vec3_t Direction); + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// +// print message on the local console +void trap_Print ( const char *fmt ); + +// abort the game +void trap_Error ( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds ( void ); + +// console variable interaction +void trap_Cvar_Register ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags, float MinValue, float MaxValue ); +void trap_Cvar_Update ( vmCvar_t *vmCvar ); +void trap_Cvar_Set ( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer ( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc ( void ); +void trap_Argv ( int n, char *buffer, int bufferLength ); +void trap_Args ( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile ( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read ( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write ( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile ( fileHandle_t f ); +int trap_FS_GetFileList ( const char *path, const char *extension, char *listbuf, int bufsize ); + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand ( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand ( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand ( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen ( void ); + +// model collision +void trap_CM_LoadMap ( const char *mapname, qboolean SubBSP ); +int trap_CM_NumInlineModels ( void ); +clipHandle_t trap_CM_InlineModel ( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel ( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents ( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents ( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask, const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments ( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StopAllSounds ( void ); +void trap_S_StartSound ( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx, int volume, int radius ); +void trap_S_StopLoopingSound ( int entnum ); + +// a local sound is always played full volume +void trap_S_StartLocalSound ( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds ( qboolean killall ); +void trap_S_AddLoopingSound ( int entityNum, const vec3_t origin, const vec3_t velocity, float radius, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound ( int entityNum, const vec3_t origin, const vec3_t velocity, float radius, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition ( int entityNum, const vec3_t origin ); + +// repatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize ( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound ( const char *sample); // returns buzz if not found +void trap_S_StartBackgroundTrack ( const char *intro, const char *loop, qboolean bReturnWithoutStarting); // empty name stops music +void trap_S_StopBackgroundTrack ( void ); + +void trap_AS_AddPrecacheEntry ( const char *set); +void trap_AS_ParseSets ( void ); +void trap_AS_UpdateAmbientSet ( const char *name, vec3_t origin); +int trap_AS_AddLocalSet ( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time); +sfxHandle_t trap_AS_GetBModelSound ( const char *name, int stage); + + +void trap_R_LoadWorldMap ( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel ( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin ( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader ( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip ( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterFont ( const char *fontName ); + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene ( void ); +void trap_R_ClearDecals ( void ); +void trap_R_AddRefEntityToScene ( const refEntity_t *re ); + +int trap_R_GetTextWidth ( const char* text, qhandle_t font, float scale, int limit ); +int trap_R_GetTextHeight ( const char* text, qhandle_t font, float scale, int limit ); +void trap_R_DrawText ( int x, int y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags ); +void trap_R_DrawTextWithCursor ( int x, int y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags, int cursorPos, char cursor ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene ( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene ( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +void trap_R_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); +void trap_R_AddLightToScene ( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint ( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene ( const refdef_t *fd ); +void trap_R_DrawVisualOverlay ( visual_t type, qboolean preProcess, float parm1, float parm2 ); +void trap_R_SetColor ( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic ( float x, float y, float w, float h, float s1, float t1, float s2, float t2, const float* color, qhandle_t hShader ); +void trap_R_ModelBounds ( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag ( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); + +// Does weird, barely controllable rotation behaviour +void trap_R_DrawRotatePic ( float x, float y, float w, float h, float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); + +// rotates image around exact center point of passed in coords +void trap_R_DrawRotatePic2 ( float x, float y, float w, float h, float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +void trap_R_RemapShader ( const char *oldShader, const char *newShader, const char *timeOffset ); + +void trap_R_GetLightStyle ( int style, color4ub_t color); +void trap_R_SetLightStyle ( int style, int color); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + + +qboolean trap_GetDefaultState(int entityIndex, entityState_t *state ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +void trap_RW_SetTeam(int team, qboolean dead); + +void trap_ResetAutorun ( void ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +// Cinematic playing +int trap_CIN_PlayCinematic ( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic ( int handle); +e_status trap_CIN_RunCinematic ( int handle); +void trap_CIN_DrawCinematic ( int handle); +void trap_CIN_SetExtents ( int handle, int x, int y, int w, int h); + +// Special FX system traps +int trap_FX_InitSystem ( refdef_t* ); +int trap_FX_RegisterEffect ( const char *file); +void trap_FX_PlaySimpleEffect ( const char *file, vec3_t org, int vol, int rad ); // uses a default up axis +void trap_FX_PlayEffect ( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffect ( const char *file, vec3_t org, vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ); +void trap_FX_PlaySimpleEffectID ( int id, vec3_t org, int vol, int rad ); // uses a default up axis +void trap_FX_PlayEffectID ( int id, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffectID ( int id, vec3_t org, vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ); +void trap_FX_PlayBoltedEffectID (int id, CFxBoltInterface *obj, int vol, int rad ); +void trap_FX_AddScheduledEffects ( void ); +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ); +qboolean trap_FX_FreeSystem ( void ); +void trap_FX_AdjustTime ( int time ); +void trap_FX_Reset ( void ); +void trap_FX_AddLine ( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags); + +int trap_MemoryRemaining ( void ); + +// Keycatcher traps +qboolean trap_Key_IsDown ( int keynum ); +int trap_Key_GetCatcher ( void ); +void trap_Key_SetCatcher ( int catcher ); +int trap_Key_GetKey ( const char *binding ); + +// RMG traps +void trap_RMG_Init ( int terrainID, const char *terrainInfo ); + + +void trap_SnapVector( float *v ); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ); + +#define G2SURFACEFLAG_ISBOLT 0x00000001 +#define G2SURFACEFLAG_OFF 0x00000002 // saves strcmp()ing for "_off" in surface names +#define G2SURFACEFLAG_SPARE0 0x00000004 // future-expansion fields, saves invalidating models if we add more +#define G2SURFACEFLAG_SPARE1 0x00000008 // +#define G2SURFACEFLAG_SPARE2 0x00000010 // +#define G2SURFACEFLAG_SPARE3 0x00000020 // +#define G2SURFACEFLAG_SPARE4 0x00000040 // +#define G2SURFACEFLAG_SPARE5 0x00000080 // +// +#define G2SURFACEFLAG_NODESCENDANTS 0x00000100 // ingame-stuff, never generated by Carcass.... +#define G2SURFACEFLAG_GENERATED 0x00000200 // + + +void trap_G2API_CollisionDetect ( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position,int frameNumber, int entNum, const vec3_t rayStart, const vec3_t rayEnd, const vec3_t scale, int traceFlags, int useLod ); + + +// CG specific API access +void trap_G2_ListModelSurfaces(void *ghlInfo); +void trap_G2_ListModelBones(void *ghlInfo, int frame); +int trap_G2API_AddBolt(void *ghoul2, const int modelIndex, const char *boneName); +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo); +qboolean trap_G2API_RemoveBolt(void *ghlInfo, const int modelIndex, const int index); +qboolean trap_G2API_AttachG2Model(void *ghoul2From, int modelFrom, void *ghoul2To, int toBoltIndex, int toModel); +qboolean trap_G2API_DetachG2Model(void *ghoul2, int modelIndex); +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList); +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2); +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias); +qboolean trap_G2API_GetAnimFileNameIndex (TGhoul2 ghoul2, qhandle_t modelIndex, const char* name ); + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex); +int trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo); +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To); +qboolean trap_G2API_RemoveGhoul2Model(void **ghlInfo, int modelIndex); +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr); + +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ); +char *trap_G2API_GetGLAName(void *ghoul2, int modelIndex); + +qboolean trap_G2API_SetBoneAnim( void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ); +qboolean trap_G2API_GetBoneAnim( void *ghoul2, const int modelIndex, const char *boneName, const int currentTime, float* frame ); + +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const int modelIndex, const char *surfaceName, const int flags); +qboolean trap_G2API_SetRootSurface(void **ghoul2, const int modelIndex, const char *surfaceName); +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int modelIndex, const int boltIndex); +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore); +void trap_G2API_ClearSkinGore ( TGhoul2 ghoul2 ); +qboolean trap_G2API_SetGhoul2ModelFlags(void *ghlInfo,int flags); +int trap_G2API_GetGhoul2ModelFlags(void *ghlInfo); +qboolean trap_G2API_SetGhoul2ModelFlagsByIndex(void *ghoul2,int index,int flags); +int trap_G2API_GetGhoul2ModelFlagsByIndex(void *ghoul2,int index); +int trap_G2API_GetNumModels(TGhoul2 ghoul2); +int trap_G2API_FindBoltIndex(TGhoul2 ghoul2, const int modelIndex, const char *boneName); +int trap_G2API_GetBoltIndex(TGhoul2 ghoul2, const int modelIndex); + +qhandle_t trap_G2API_RegisterSkin ( const char *skinName, int numPairs, const char *skinPairs); +qboolean trap_G2API_SetSkin ( TGhoul2 ghoul2, int modelIndex, qhandle_t customSkin); + +void trap_MAT_Init(void); +void trap_MAT_Reset(void); +sfxHandle_t trap_MAT_GetSound(char *key, int material); +qhandle_t trap_MAT_GetDecal(char *key, int material); +const float trap_MAT_GetDecalScale(char *key, int material); +qhandle_t trap_MAT_GetEffect(char *key, int material); +qhandle_t trap_MAT_GetDebris(char *key, int material); +const float trap_MAT_GetDebrisScale(char *key, int material); + +#define MAT_FOOTSTEP_NORMAL "footstep" +#define MAT_FOOTSTEP_STEALTH "footstepStealth" +#define MAT_FOOTSTEP_PRONE "footstepProne" +#define MAT_AMMO_556 "5.56mm" +#define MAT_AMMO_9 "9mm" +#define MAT_AMMO_12 "12 gauge" +#define MAT_AMMO_045 "0.45 ACP" +#define MAT_AMMO_762 "7.62mm belt" +#define MAT_AMMO_127 "12.7mm" +#define MAT_LAND_NORMAL "land" +#define MAT_LAND_PAIN "land_pain" +#define MAT_LAND_DEATH "land_death" +#define MAT_BOUNCEMETAL_1 "bouncemetal0" +#define MAT_BOUNCEMETAL_2 "bouncemetal1" + + +void trap_CM_TM_Create(int terrainID); +void trap_CM_TM_AddBuilding(int x, int y, int side); +void trap_CM_TM_AddSpot(int x, int y); +void trap_CM_TM_AddTarget(int x, int y, float radius); +void trap_CM_TM_Upload(const vec3_t origin, const vec3_t angle); +void trap_CM_TM_ConvertPosition(void); + +int trap_CM_RegisterTerrain(const char *config); +void trap_RE_InitRendererTerrain( const char *info ); +void trap_CG_RegisterSharedMemory(char *memory); + +// Memory allocation (note this memory is allocated from cgames vm hunk) +void* trap_VM_LocalAlloc ( int size ); +void* trap_VM_LocalAllocUnaligned ( int size ); // WARNING!!!! USE WITH CAUTION!!! BEWARE OF DOG!!! +void* trap_VM_LocalTempAlloc ( int size ); +void trap_VM_LocalTempFree ( int size ); // free must be in opposite order of allocation! +const char* trap_VM_LocalStringAlloc ( const char *source ); + +// Direct communication with the UI module +void trap_UI_CloseAll ( void ); +void trap_UI_SetActiveMenu ( int menu ); + + +void CG_Init_CG(void); +void CG_Init_CGents(void); + + +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent); +void CG_AddProcGore(centity_t *cent); +void CG_PredictedProcGore ( int weaponnum, int attack, vec3_t start, vec3_t end, centity_t* cent ); + +qboolean G2_PositionEntityOnBolt(refEntity_t *ent, + void *ghoul2, int modelIndex, int boltIndex, + vec3_t origin, vec3_t angles, vec3_t modelScale); + + +void CG_MiscEnt ( void ); +void CG_DrawMiscEnts ( void ); +qboolean CG_ParseGametypeFile ( void ); + diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c new file mode 100644 index 0000000..1becff4 --- /dev/null +++ b/code/cgame/cg_localents.c @@ -0,0 +1,594 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, shells, etc. + +#include "cg_local.h" + +#define MAX_LOCAL_ENTITIES 512 +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for map restarts +=================== +*/ +void CG_InitLocalEntities( void ) +{ + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) + { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) +{ + if ( !le->prev ) + { + Com_Error( ERR_FATAL, "CG_FreeLocalEntity: not active" ); + } + + if (le->refEntity.ghoul2) + { + trap_G2API_CleanGhoul2Models(&le->refEntity.ghoul2); + } + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) + { + radius = 16 + (rand()&31); + + trap_R_AddDecalToScene ( cgs.media.bloodMarkShader, trace->endpos, + trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + + } + else if ( le->leMarkType == LEMT_BURN ) + { + radius = 8 + (rand()&15); + + trap_R_AddDecalToScene ( cgs.media.burnMarkShader, trace->endpos, + trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } + + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) +{ + if ( le->leBounceSoundType == LEBS_BRASS ) + { + } + + // don't allow a fragment to make multiple bounce sounds, + // or it gets too noisy as they settle + le->leBounceSoundType = LEBS_NONE; +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg.time; + + + // check for stop, making sure that even on low FPS systems it doesn't bobble + if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { + le->pos.trType = TR_STATIONARY; + } else { + + } +} + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) + { + CG_FreeLocalEntity( le ); + return; + } + + if (!trace.startsolid) + { + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); + } +} + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); + } + else { + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + } + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + re->radius = le->radius * ( 1.0 - c ) + 8; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) +{ + if (le->endTime < cg.time) + { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + + +/* +=================== +CG_AddLine +=================== +*/ +void CG_AddLine( localEntity_t *le ) +{ + refEntity_t *re; + + re = &le->refEntity; + + re->reType = RT_LINE; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================ +CG_AddGib +================ +*/ +void CG_AddGib( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + le->refEntity.origin[2] += le->zOffset; + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + le->refEntity.origin[2] -= le->zOffset; + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + le->refEntity.origin[2] += le->zOffset; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] -= le->zOffset; + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) + { + CG_FreeLocalEntity( le ); + return; + } + + if (!trace.startsolid) + { + // leave a mark +// CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound +// CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + le->refEntity.origin[2] += le->zOffset; + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] -= le->zOffset; + } +} + +//============================================================================== + +/* +=================== +CG_AddLocalEntities +=================== +*/ +void CG_AddLocalEntities( void ) +{ + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) + { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg.time >= le->endTime ) + { + CG_FreeLocalEntity( le ); + continue; + } + + switch ( le->leType ) + { + default: + Com_Error( ERR_FATAL, "Bad leType: %i", le->leType ); + break; + + case LE_FRAGMENT: + CG_AddFragment( le ); + break; + + case LE_MOVE_SCALE_FADE: + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: + CG_AddScaleFade( le ); + break; + + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; + + case LE_LINE: + CG_AddLine( le ); + break; + + case LE_GIB: + CG_AddGib(le); + break; + } + } +} + + + + diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c new file mode 100644 index 0000000..e5273c5 --- /dev/null +++ b/code/cgame/cg_main.c @@ -0,0 +1,1953 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_main.c -- initialization and primary entry point for cgame + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +// display context for new ui stuff +displayContextDef_t cgDC; + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + + +int forceModelModificationCount = -1; + +void CG_Init ( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_InitItems ( void ); +void CG_Shutdown ( void ); + +void CG_CalcEntityLerpPositions( centity_t *cent ); + +static int C_PointContents (void); +static void C_GetLerpOrigin (void); +static void C_GetLerpAngles (void); +static void C_GetModelScale (void); +static void C_Trace (void); +static void C_CameraShake (void); + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) +{ + switch ( command ) + { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + + case CG_KEY_EVENT: + CG_KeyEvent(arg0, arg1); + return 0; + + case CG_MOUSE_EVENT: + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; + + CG_MouseEvent(arg0, arg1); + return 0; + + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + + case CG_POINT_CONTENTS: + return C_PointContents(); + + case CG_GET_LERP_ORIGIN: + C_GetLerpOrigin(); + return 0; + + case CG_GET_LERP_ANGLES: + C_GetLerpAngles(); + return 0; + + case CG_GET_MODEL_SCALE: + C_GetModelScale(); + return 0; + + case CG_GET_GHOUL2: + return (int)CG_GetEntity (arg0)->ghoul2; + + case CG_GET_MODEL_LIST: + return (int)cgs.gameModels; + + case CG_CALC_LERP_POSITIONS: + CG_CalcEntityLerpPositions( CG_GetEntity ( arg0 ) ); + return 0; + + case CG_TRACE: + C_Trace(); + return 0; + + case CG_GET_ORIGIN: + VectorCopy( CG_GetEntity (arg0)->currentState.pos.trBase, (float *)arg1); + return 0; + + case CG_GET_ANGLES: + VectorCopy( CG_GetEntity (arg0)->currentState.apos.trBase, (float *)arg1); + return 0; + + case CG_GET_ORIGIN_TRAJECTORY: + return (int)&CG_GetEntity (arg0)->nextState.pos; + + case CG_GET_ANGLE_TRAJECTORY: + return (int)&CG_GetEntity (arg0)->nextState.apos; + + case CG_FX_CAMERASHAKE: + C_CameraShake(); + return 0; + + case CG_MISC_ENT: + CG_MiscEnt(); + return 0; + + case CG_MAP_CHANGE: + // this trap map be called more than once for a given map change, as the + // server is going to attempt to send out multiple broadcasts in hopes that + // the client will receive one of them + cg.mMapChange = qtrue; + trap_S_ClearLoopingSounds ( qtrue ); + trap_S_StopAllSounds ( ); + trap_UI_CloseAll ( ); + return 0; + + default: + Com_Error( ERR_FATAL, "vmMain: unknown command %i", command ); + break; + } + + return -1; +} + +static int C_PointContents(void) +{ + TCGPointContents *data = (TCGPointContents *)cg.sharedBuffer; + + return CG_PointContents( data->mPoint, data->mPassEntityNum ); +} + +static void C_GetLerpOrigin(void) +{ + TCGVectorData *data = (TCGVectorData *)cg.sharedBuffer; + + VectorCopy( CG_GetEntity (data->mEntityNum)->lerpOrigin, data->mPoint); +} + +static void C_GetLerpAngles(void) +{ + TCGVectorData *data = (TCGVectorData *)cg.sharedBuffer; + + VectorCopy(CG_GetEntity(data->mEntityNum)->lerpAngles, data->mPoint); +} + +static void C_GetModelScale(void) +{ + TCGVectorData *data = (TCGVectorData *)cg.sharedBuffer; + + VectorCopy(CG_GetEntity(data->mEntityNum)->modelScale, data->mPoint); +} + +static void C_Trace(void) +{ + TCGTrace *td = (TCGTrace *)cg.sharedBuffer; + + CG_Trace(&td->mResult, td->mStart, td->mMins, td->mMaxs, td->mEnd, td->mSkipNumber, td->mMask); +} + +static void C_CameraShake(void) +{ + TCGCameraShake *data = (TCGCameraShake *)cg.sharedBuffer; + + CG_CameraShake ( data->mOrigin, data->mIntensity, data->mRadius, data->mTime ); +} + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +centity_t *cg_permanents[MAX_GENTITIES]; +int cg_numpermanents = 0; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + +vmCvar_t con_notifyTime; +vmCvar_t cg_centertime; +vmCvar_t cg_centerY; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_shadows; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRadar; +vmCvar_t cg_drawTeamScores; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairRGBA; +vmCvar_t cg_crosshairFriendRGBA; +vmCvar_t cg_draw2D; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_goreDetail; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonYaw; +vmCvar_t cg_thirdPersonPitch; +vmCvar_t cg_thirdPersonHorzOffset; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_chatTime; +vmCvar_t cg_chatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_predictItems; +vmCvar_t cg_antiLag; +vmCvar_t cg_impactPrediction; +vmCvar_t cg_autoReload; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawFriend; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescale; +vmCvar_t cg_noProjectileTrail; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_DebugGore; +vmCvar_t cg_lockBlood; +vmCvar_t cg_lockSever; +vmCvar_t cg_lockDeaths; +vmCvar_t cg_marks; +vmCvar_t cg_shellEjection; +vmCvar_t RMG_distancecull; + +vmCvar_t cg_blueteamname; +vmCvar_t cg_redteamname; + +vmCvar_t cg_damageindicator; +vmCvar_t cg_tracerChance; +vmCvar_t cg_animBlend; + +vmCvar_t cg_automap_x; +vmCvar_t cg_automap_y; +vmCvar_t cg_automap_w; +vmCvar_t cg_automap_h; +vmCvar_t cg_automap_a; + +vmCvar_t ui_info_redcount; +vmCvar_t ui_info_bluecount; +vmCvar_t ui_info_speccount; +vmCvar_t ui_info_freecount; +vmCvar_t ui_info_pickupsdisabled; +vmCvar_t ui_info_seenobjectives; + +vmCvar_t cg_voiceRadio; +vmCvar_t cg_voiceGlobal; +vmCvar_t cg_soundGlobal; +vmCvar_t cg_soundFrag; + +vmCvar_t cg_weaponMenuFast; + +vmCvar_t cg_bodyTime; + +vmCvar_t rw_enabled; + +typedef struct +{ + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; + float mMinValue, mMaxValue; +} cvarTable_t; + +static cvarTable_t cvarTable[] = +{ + { &cg_lockBlood, "lock_blood", "1", 0 }, + { &cg_lockSever, "lock_sever", "1", 0 }, + { &cg_lockDeaths, "lock_deaths", "1", 0 }, + { &rw_enabled, "rw_enabled", "0", 0 }, + + { &con_notifyTime, "con_notifyTime", "3", 0 }, + + { &cg_shellEjection, "cg_shellEjection", "1", CVAR_ARCHIVE }, + { &cg_ignore, "cg_ignore", "0", 0 }, + { &cg_autoswitch, "cg_autoswitch", "2", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "80", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 80.0, 100.0 }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE|CVAR_CHEAT }, + { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_crosshairRGBA, "cg_crosshairRGBA", "1,1,1,1", CVAR_ARCHIVE }, + { &cg_crosshairFriendRGBA, "cg_crosshairFriendRGBA", "1,0,0,1", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "4", CVAR_ARCHIVE }, + { &cg_centerY, "cg_centerY", "70", CVAR_ARCHIVE }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE|CVAR_LOCK_RANGE, 0.0f, 0.002f }, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE|CVAR_LOCK_RANGE, 0.0f, 0.005f }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_ARCHIVE|CVAR_LOCK_RANGE, 0.0f, 0.005f }, + { &cg_bobpitch, "cg_bobpitch", "0.001", CVAR_ARCHIVE|CVAR_LOCK_RANGE, 0.0f, 0.001f }, + { &cg_bobroll, "cg_bobroll", "0.001", CVAR_ARCHIVE|CVAR_LOCK_RANGE, 0.0f, 0.001f }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + + { &cg_goreDetail, "cg_goreDetail", "1", CVAR_ARCHIVE }, + + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_CHEAT }, + { &cg_thirdPersonYaw, "cg_thirdPersonYaw", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitch, "cg_thirdPersonPitch", "15", CVAR_CHEAT }, + + { &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", CVAR_CHEAT }, + + { &cg_chatTime, "cg_chatTime", "8000", CVAR_ARCHIVE }, + { &cg_chatHeight, "cg_chatHeight", "4", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_antiLag, "cg_antiLag", "1", CVAR_ARCHIVE | CVAR_USERINFO }, +#ifdef _SOF2_FLESHIMPACTPREDICTION + { &cg_impactPrediction, "cg_impactPrediction", "2", CVAR_ARCHIVE }, +#else + { &cg_impactPrediction, "cg_impactPrediction", "1", CVAR_ARCHIVE }, +#endif + { &cg_autoReload, "cg_autoReload", "1", CVAR_ARCHIVE | CVAR_USERINFO }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + // the following variables are created in other parts of the system, + // but we also reference them here + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo + + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescale, "timescale", "1", 0}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, + + { &cg_DebugGore, "cg_debuggore", "", CVAR_CHEAT }, + { &RMG_distancecull, "RMG_distancecull", "5000", CVAR_CHEAT }, + + { &cg_blueteamname, "ui_blueteamname", "0", CVAR_ROM|CVAR_INTERNAL }, + { &cg_redteamname, "ui_redteamname", "0", CVAR_ROM|CVAR_INTERNAL }, + + { &cg_damageindicator, "cg_damageindicator", "1.5", CVAR_ARCHIVE }, + { &cg_tracerChance, "cg_tracerChance", "1", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 0.0f, 0.50f }, + + { &cg_animBlend, "cg_animBlend", "1", CVAR_ARCHIVE }, + + { &cg_automap_x, "cg_automap_x", "485", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 0.0, 629.0 }, + { &cg_automap_y, "cg_automap_y", "5", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 0.0, 469.0}, + { &cg_automap_w, "cg_automap_w", "150", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 10.0, 640.0 }, + { &cg_automap_h, "cg_automap_h", "150", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 10.0, 480.0}, + { &cg_automap_a, "cg_automap_a", "0.75", CVAR_ARCHIVE | CVAR_LOCK_RANGE, 0.0, 1.0}, + + { &cg_drawRadar, "cg_drawRadar", "2", CVAR_ARCHIVE }, + { &cg_drawTeamScores, "cg_drawTeamScores", "1", CVAR_ARCHIVE }, + + { &ui_info_redcount, "ui_info_redcount", "0", CVAR_INTERNAL|CVAR_ROM }, + { &ui_info_bluecount, "ui_info_bluecount", "0", CVAR_INTERNAL|CVAR_ROM }, + { &ui_info_speccount, "ui_info_speccount", "0", CVAR_INTERNAL|CVAR_ROM }, + { &ui_info_freecount, "ui_info_freecount", "0", CVAR_INTERNAL|CVAR_ROM }, + { &ui_info_pickupsdisabled, "ui_info_pickupsdisabled", "0", CVAR_INTERNAL|CVAR_ROM }, + { &ui_info_seenobjectives, "ui_info_seenobjectives", "0", CVAR_INTERNAL|CVAR_ROM }, + + { &cg_voiceRadio, "cg_voiceRadio", "1", CVAR_ARCHIVE, 0.0f, 0.0f }, + { &cg_voiceGlobal, "cg_voiceGlobal", "1", CVAR_ARCHIVE, 0.0f, 0.0f }, + { &cg_soundGlobal, "cg_soundGlobal", "1", CVAR_ARCHIVE, 0.0f, 0.0f }, + { &cg_soundFrag, "cg_soundFrag", "1", CVAR_ARCHIVE, 0.0f, 0.0f }, + + { &cg_weaponMenuFast, "cg_weaponMenuFast", "0", CVAR_ARCHIVE }, + + { &cg_bodyTime, "cg_bodyTime", "0", CVAR_ARCHIVE, 0.0f, 0.0f }, +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) +{ + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags, cv->mMinValue, cv->mMaxValue ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register(NULL, "identity", DEFAULT_IDENTITY, CVAR_USERINFO | CVAR_ARCHIVE, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "team_identity", DEFAULT_IDENTITY, CVAR_USERINFO | CVAR_ARCHIVE, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "outfitting", "AAAA", CVAR_USERINFO | CVAR_ARCHIVE, 0.0, 0.0 ); + + // Cvars uses for transferring data between client and server + trap_Cvar_Register(NULL, "ui_about_gametype", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_gametypename", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_scorelimit", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_timelimit", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_maxclients", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_dmflags", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_mapname", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_hostname", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_needpass", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_about_botminplayers", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_info_availableweapons","0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + + trap_Cvar_Register(NULL, "ui_info_redscore", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + trap_Cvar_Register(NULL, "ui_info_bluescore", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + + trap_Cvar_Register(NULL, "cg_lastobjectives", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + + trap_Cvar_Register(NULL, "ui_about_seed", "0", CVAR_ROM|CVAR_INTERNAL, 0.0, 0.0 ); + + trap_Cvar_Set ( "ui_info_seenobjectives", "0" ); + trap_Cvar_Set ( "ui_info_redscore", "0" ); + trap_Cvar_Set ( "ui_info_bluescore", "0" ); +} + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) +{ + int i; + + for (i=0 ; iinfoValid ) + { + continue; + } + + counts[cl->team]++; + } + + trap_Cvar_Set ( "ui_info_redcount", va("%i", counts[TEAM_RED] ) ); + trap_Cvar_Set ( "ui_info_bluecount", va("%i", counts[TEAM_BLUE]) ); + trap_Cvar_Set ( "ui_info_freecount", va("%i", counts[TEAM_FREE]) ); + trap_Cvar_Set ( "ui_info_speccount", va("%i", counts[TEAM_SPECTATOR] ) ); +} + +/* +================= +CG_UpdateCvars +================= +*/ +void CG_UpdateCvars( void ) +{ + int i; + cvarTable_t *cv; + + static int crosshairRGBAModificationCount = -1; + static int crosshairFriendRGBAModificationCount = -1; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) + { + trap_Cvar_Update( cv->vmCvar ); + } + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) + { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } + + // If the crosshair RGBA was modified then update the globals + if ( crosshairRGBAModificationCount != cg_crosshairRGBA.modificationCount ) + { + sscanf ( cg_crosshairRGBA.string, "%f,%f,%f,%f", + &cg.crosshairRGBA[0], + &cg.crosshairRGBA[1], + &cg.crosshairRGBA[2], + &cg.crosshairRGBA[3] ); + + crosshairRGBAModificationCount = cg_crosshairRGBA.modificationCount; + } + + // If the crosshair RGBA was modified then update the globals + if ( crosshairFriendRGBAModificationCount != cg_crosshairFriendRGBA.modificationCount ) + { + sscanf ( cg_crosshairFriendRGBA.string, "%f,%f,%f,%f", + &cg.crosshairFriendRGBA[0], + &cg.crosshairFriendRGBA[1], + &cg.crosshairFriendRGBA[2], + &cg.crosshairFriendRGBA[3] ); + + crosshairFriendRGBAModificationCount = cg_crosshairFriendRGBA.modificationCount; + } +} + +int CG_CrosshairPlayer( void ) +{ + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) + { + return -1; + } + return cg.crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +void QDECL Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +} + +#endif + +/* +================= +CG_GetEntity + +This function handles the predicted player entitiy specially. The player +should always be using the predicted entity rather than whats in the main +entity array. +================= +*/ +centity_t* CG_GetEntity ( int index ) +{ +/* + if ( index == cg.clientNum ) + { + return &cg.predictedPlayerEntity; + } +*/ + + return &cg_entities[index]; +} + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if (!s || !s[0]) + { + return; + } + + while (*s) { + start = s; + while (*s && *s != ' ') + { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) + { + Com_Error( ERR_FATAL, "PrecacheItem: %s has bad precache string", item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) + { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) + { + trap_S_RegisterSound( data ); + } + } +} + +/* +================= +CG_RegisterLadders + +Registers all the ladders in the current map +================= +*/ +static void CG_RegisterLadders ( void ) +{ + int i; + const char *ladder; + + for ( i = 0 ; i < MAX_LADDERS ; i++ ) + { + vec3_t absmin; + vec3_t absmax; + vec3_t fwd; + vec3_t angles; + + ladder = CG_ConfigString( CS_LADDERS + i ); + + // No ladder? + if ( !ladder || !ladder[0] ) + { + continue; + } + + VectorClear ( angles ); + sscanf ( ladder, "%f,%f,%f,%f,%f,%f,%f", + &absmin[0], &absmin[1], &absmin[2], + &absmax[0], &absmax[1], &absmax[2], + &angles[YAW] ); + + AngleVectors( angles, fwd, 0, 0); + + BG_AddLadder ( absmin, absmax, fwd ); + } +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) +{ + int i; + char items[MAX_ITEMS+1]; + const char *soundName; + + // voice commands + CG_LoadVoiceChats(); + + CG_LoadingStage ( 3 ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav" ); + + cgs.media.waterFootstep[0] = trap_S_RegisterSound( "sound/player/steps/water/water_step0"); + cgs.media.waterFootstep[1] = trap_S_RegisterSound( "sound/player/steps/water/water_step1"); + + cgs.media.waterWade[0] = trap_S_RegisterSound( "sound/player/steps/water/water_wade0"); + cgs.media.waterWade[1] = trap_S_RegisterSound( "sound/player/steps/water/water_wade1"); + + cgs.media.waterJumpIn = trap_S_RegisterSound( "sound/player/jumps/water_jump"); + cgs.media.waterLeave = trap_S_RegisterSound( "sound/pain_death/mullins/water_surface"); + + cgs.media.armorHitSound[0] = trap_S_RegisterSound( "sound/pain_death/mullins/armor01"); + cgs.media.armorHitSound[1] = trap_S_RegisterSound( "sound/pain_death/mullins/armor02"); + cgs.media.fleshHitSound[0] = trap_S_RegisterSound( "sound/pain_death/mullins/hit01"); + cgs.media.fleshHitSound[1] = trap_S_RegisterSound( "sound/pain_death/mullins/hit02"); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + CG_LoadingStage ( 4 ); + + for (i = 0; i < MAX_AMBIENT_SOUNDSETS; i++) // 0 is reserved for current + { + const char *psString = CG_ConfigString( CS_AMBIENT_SOUNDSETS + i); + + if (psString && strlen(psString)) + { + trap_AS_AddPrecacheEntry( psString ); + } + else + { +// break; // EOL + } + } + + trap_AS_ParseSets(); + + for ( i = 1 ; i < bg_numItems ; i++ ) + { + CG_RegisterItemSounds( i ); + } + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) + { + soundName = CG_ConfigString( CS_SOUNDS+i ); + + if ( !soundName[0] ) + { + break; + } + + // custom sound + if ( soundName[0] == '*' ) + { + continue; + } + + cgs.gameSounds[i] = trap_S_RegisterSound( soundName ); + } + + + cgs.media.glassBreakSound = trap_S_RegisterSound("sound/effects/glassbreak1.wav"); + + // Respawn sound is only used in gametypes where you respawn + if ( cgs.gametypeData->respawnType != RT_NONE ) + { + cgs.media.respawnSound = trap_S_RegisterSound("sound/player_respawn.wav"); + } + + cgs.media.itemRespawnSound = trap_S_RegisterSound ( "sound/item_respawn.mp3" ); + + cgs.media.fragSound = trap_S_RegisterSound ( "sound/frag.mp3" ); + cgs.media.fragSelfSound = trap_S_RegisterSound ( "sound/self_frag.mp3" ); + cgs.media.zoomSound = trap_S_RegisterSound ( "sound/weapons/sniper/scope_zoom.wav" ); + cgs.media.gogglesOnSound = trap_S_RegisterSound ( "sound/weapons/goggles/turn_on.mp3" ); + cgs.media.gogglesOffSound = trap_S_RegisterSound ( "sound/weapons/goggles/turn_off.mp3" ); + + // Move out sound + if ( cgs.gametypeData->respawnType == RT_NONE ) + { + cgs.media.goSound = trap_S_RegisterSound ( "sound/radio/male/move.mp3" ); + } + + // bullet flyby sounds + for (i = 0; i < NUMFLYBYS; i++) + { + cgs.media.flybySounds[i] = trap_S_RegisterSound ( va("sound/player/bullet_impacts/whine/b_whine%d", i)); + } + + // Water sounds + cgs.media.drownPainSound[0] = trap_S_RegisterSound ( "sound/pain_death/mullins/drown01.mp3" ); + cgs.media.drownPainSound[1] = trap_S_RegisterSound ( "sound/pain_death/mullins/drown02.mp3" ); + cgs.media.drownDeathSound = trap_S_RegisterSound ( "sound/pain_death/mullins/drown_dead.mp3" ); + + CG_LoadingStage ( 5 ); +} + + +//------------------------------------- +// CG_RegisterEffects +// +// Handles precaching all effect files +// and any shader, model, or sound +// files an effect may use. +//------------------------------------- +static void CG_RegisterEffects( void ) +{ + const char *effectName; + int i; + + for ( i = 1 ; i < MAX_FX ; i++ ) + { + effectName = CG_ConfigString( CS_EFFECTS + i ); + + if ( !effectName[0] ) + { + break; + } + + trap_FX_RegisterEffect( effectName ); + } +} + +//=================================================================================== + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) +{ + int i; + char items[MAX_ITEMS+1]; + int sub; + char temp[MAX_QPATH]; + int breakPoint; + const char *terrainInfo; + int terrainID; + vec3_t distance; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + // Load the map + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + // precache status bar pics + CG_LoadingString( "game media" ); + + CG_LoadingStage ( 6 ); + + cgs.media.cursor = trap_R_RegisterShaderNoMip( "gfx/menus/cursor/cursor" ); + + // Register the graphics for the scoreboard + cgs.media.scoreboard = trap_R_RegisterShaderNoMip ( "gfx/menus/scoreboard/scoreboard.tga" ); + cgs.media.scoreboardHeader = trap_R_RegisterShaderNoMip ( "gfx/menus/scoreboard/scorelineheader.tga" ); + cgs.media.scoreboardLine = trap_R_RegisterShaderNoMip ( "gfx/menus/scoreboard/scoreline.tga" ); + cgs.media.scoreboardFooter = trap_R_RegisterShaderNoMip ( "gfx/menus/scoreboard/scorelinefooter.tga" ); + cgs.media.scoreboardTotals = trap_R_RegisterShaderNoMip ( "gfx/menus/scoreboard/scorelinetotals.tga" ); + + cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); + cgs.media.lagometerShader = trap_R_RegisterShader("gfx/menus/hud/lagometer.tga" ); + cgs.media.disconnectShader = trap_R_RegisterShader("gfx/menus/hud/disconnect.tga" ); + cgs.media.waterBubbleShader = trap_R_RegisterShader( "gfx/misc/bubble" ); + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + + cgs.media.mAutomapPlayerIcon = trap_R_RegisterShader( "gfx/menus/rmg/arrow_w" ); + + CG_LoadingStage ( 7 ); + + Com_Printf( S_COLOR_CYAN "---------- Fx System Initialization ---------\n" ); + trap_FX_InitSystem( &cg.refdef); + + Com_Printf( S_COLOR_CYAN "----- Fx System Initialization Complete -----\n" ); + CG_RegisterEffects(); + + // Initialize the materials + trap_MAT_Init(); + + CG_LoadingStage ( 8 ); + + // Initialize the crosshairs + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) + { + cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/menus/crosshairs/ch%i", i) ); + } + + CG_LoadingStage ( 9 ); + + if ( cgs.gametypeData->teams ) + { + cgs.media.blueFriendShader = trap_R_RegisterShader ( "gfx/menus/hud/team_blue" ); + cgs.media.redFriendShader = trap_R_RegisterShader ( "gfx/menus/hud/team_red" ); + cgs.media.radarShader = trap_R_RegisterShaderNoMip ( "gfx/menus/hud/radar.png" ); + } + + cgs.media.botSmallShader = trap_R_RegisterShaderNoMip ( "gfx/menus/hud/bot_small" ); + cgs.media.armorShader = trap_R_RegisterShaderNoMip ( "gfx/menus/hud/hud_armor" ); + cgs.media.deadShader = trap_R_RegisterShaderNoMip ( "gfx/menus/hud/dead" ); + + CG_LoadingStage ( 10 ); + + // Initialize weapons, load them all + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + for (i = WP_NONE + 1; i < WP_NUM_WEAPONS; i ++ ) + { + if ( cgs.pickupsDisabled && !BG_IsWeaponAvailableForOutfitting ( i, 2 ) ) + { + continue; + } + + CG_RegisterWeapon ( i ); + } + + CG_LoadingStage ( 11 ); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) + { + // Dont bother with weapons, theyare already loaded + + if ( items[ i ] == '1' || cg_buildScript.integer ) + { + CG_RegisterItemVisuals( i ); + } + } + + CG_LoadingStage ( 12 ); + + // wall marks + cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burnmark5" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + cgs.media.mAutomap = 0; + + // register the inline models + breakPoint = cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 0 ; i < cgs.numInlineModels ; i++ ) + { + char name[10]; + vec3_t mins; + vec3_t maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + if (!cgs.inlineDrawModel[i]) + { + breakPoint = i; + break; + } + + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) + { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + // register all the server specified models + for (i=1 ; i= MAX_CONFIGSTRINGS ) + { + Com_Error( ERR_FATAL, "CG_ConfigString: bad index: %i", index ); + } + + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( qboolean bForceStart ) +{ + const char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + if (!parm2[0]) + { + strcpy(parm2, parm1); + } + if ( *parm1 && *parm2 ) + { + trap_S_StartBackgroundTrack( parm1, parm2, !bForceStart ); + } +} + +char *CG_GetMenuBuffer(const char *filename) +{ + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + const char *tempStr; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "{") != 0) { + return qfalse; + } + + while ( 1 ) + { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) + { + return qtrue; + } + + // font + if (Q_stricmp(token.string, "defaultFont") == 0) + { + if (!PC_String_Parse(handle, &tempStr) ) + { + return qfalse; + } + cgDC.Assets.defaultFont = cgDC.registerFont(tempStr); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr ); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) { + if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { + return qfalse; + } + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shader") == 0) + { + if (!PC_String_Parse(handle, &tempStr)) + { + return qfalse; + } + trap_R_RegisterShaderNoMip ( tempStr ); + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + return qfalse; // bk001204 - why not? +} + +/* +====================== +CG_ParseMenu +====================== +*/ +void CG_ParseMenu ( const char *menuFile ) +{ + pc_token_t token; + int handle; + + // Load the menu file + handle = trap_PC_LoadSource(menuFile); + if (!handle) + { + return; + } + + while ( 1 ) + { + if (!trap_PC_ReadToken( handle, &token )) + { + break; + } + + if ( token.string[0] == '}' ) + { + break; + } + + if (Q_stricmp(token.string, "assetGlobalDef") == 0) + { + if (CG_Asset_Parse(handle)) + { + continue; + } + else + { + break; + } + } + + if (Q_stricmp(token.string, "menudef") == 0) + { + // start a new menu + Menu_New ( handle ); + } + } + + trap_PC_FreeSource(handle); +} + +/* +====================== +CG_LoadMenu +====================== +*/ +qboolean CG_LoadMenu ( const char **p ) +{ + char *token; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') + { + return qfalse; + } + + while ( 1 ) + { + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) + { + return qtrue; + } + + if ( !token || token[0] == 0 ) + { + return qfalse; + } + + CG_ParseMenu(token); + } + + return qfalse; +} + +/* +====================== +CG_LoadMenus +====================== +*/ +void CG_LoadMenus ( const char *menuFile ) +{ + char *token; + const char *p; + int len, start; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + start = trap_Milliseconds(); + + trap_PC_LoadGlobalDefines ( "ui/menudef.h" ); + + // Open the hud menu file + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + if ( !f ) + { + Com_Error( ERR_FATAL, va( S_COLOR_RED "hud menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + return; + } + + // Too big? + if ( len >= MAX_MENUDEFFILE ) + { + Com_Error( ERR_FATAL, va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress(buf); + + Menu_Reset(); + + p = buf; + + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') + { + break; + } + + if ( Q_stricmp( token, "}" ) == 0 ) + { + break; + } + + if (Q_stricmp(token, "loadmenu") == 0) + { + if ( CG_LoadMenu ( &p ) ) + { + continue; + } + else + { + break; + } + } + } + + trap_PC_RemoveAllGlobalDefines ( ); + + Com_Printf("HUD menu load time = %d milli seconds\n", trap_Milliseconds() - start); +} + +/* +====================== +CG_LoadMenus +====================== +*/ +static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key, const char* param ) +{ + return qfalse; +} + + +static int CG_FeederCount(float feederID) +{ + return 0; +} + +static const char *CG_FeederItemText ( float feederID, int index, int column, qhandle_t *handle) +{ + return ""; +} + +static qhandle_t CG_FeederItemImage(float feederID, int index) +{ + return 0; +} + +static void CG_FeederSelection(float feederID, int index) +{ +} + +static float CG_Cvar_Get(const char *cvar) +{ + char buff[128]; + memset(buff, 0, sizeof(buff)); + trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); + + return atof(buff); +} + +static int CG_OwnerDrawWidth(int ownerDraw, qhandle_t font, float scale ) +{ + switch (ownerDraw) + { + case CG_GAME_TYPE: + return trap_R_GetTextWidth (CG_GameTypeString(), font, scale, 0 ); + + case CG_GAME_STATUS: + return trap_R_GetTextWidth (CG_GetGameStatusText(), font, scale, 0 ); + } + + return 0; +} + +static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); +} + +static void CG_StopCinematic(int handle) { + trap_CIN_StopCinematic(handle); +} + +static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); +} + +static void CG_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} + +/* +================= +CG_LoadHudMenu +================= +*/ +void CG_LoadHudMenu() +{ + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_DrawText; + cgDC.drawTextWithCursor = &CG_DrawTextWithCursor; + cgDC.getTextWidth = &trap_R_GetTextWidth; + cgDC.getTextHeight = &trap_R_GetTextHeight; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.ownerDrawDisabled = &CG_OwnerDrawDisabled; + cgDC.runScript = &CG_RunMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + cgDC.yscale = cgDC.glconfig.vidHeight * (1.0/480.0); + cgDC.xscale = cgDC.glconfig.vidWidth * (1.0/640.0); + + Init_Display(&cgDC); + + Menu_Reset(); + + CG_LoadMenus( "ui/hud.txt" ); +} + +/* +================= +CG_AssetCache +================= +*/ +void CG_AssetCache() +{ + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff in the active snap shots +void CG_Init_CG(void) +{ + memset( &cg, 0, sizeof(cg)); +} + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff +void CG_Init_CGents(void) +{ + memset(&cg_entities, 0, sizeof(cg_entities)); +} + +void CG_InitItems(void) +{ + memset( cg_items, 0, sizeof( cg_items ) ); +} + +void CG_TransitionPermanent(void) +{ + centity_t *cent = cg_entities; + int i; + + cg_numpermanents = 0; + for(i=0;icurrentState)) + { + cent->nextState = cent->currentState; + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + cent->currentValid = qtrue; + + cg_permanents[cg_numpermanents++] = cent; + } + } +} + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) +{ + const char *s; + + // Some of the new SOF2 features required more traffic through + // the trap calls than than the original vm code would allow for. Therefore + // a block of shared memory is registered with the engine to allow for + // larger pieced of data to be transferred back and forth. + trap_CG_RegisterSharedMemory(cg.sharedBuffer); + + // clear everything + CG_Init_CGents(); + CG_Init_CG(); + CG_InitItems(); + + BG_ParseNPCFiles ( ); + + memset( &cgs, 0, sizeof( cgs ) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + + // Load these first since they are used to display the status + cgs.media.loadBulletShader = trap_R_RegisterShaderNoMip ( "gfx/menus/misc/load_bullet.tga" ); + cgs.media.loadClipShader = trap_R_RegisterShaderNoMip ( "gfx/menus/misc/load_clip.tga" ); + cgs.media.whiteShader = trap_R_RegisterShaderNoMip ( "white" ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + CG_LoadingStage ( 0 ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + // Populate the gametypeData array with the gametypes available + // in the scripts directory + BG_BuildGametypeList(); + + cg.weaponSelect = WP_USSOCOM_PISTOL; + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // Register the fonts + cgs.media.hudFont = trap_R_RegisterFont ( "hud" ); + cgs.media.lcdFont = trap_R_RegisterFont ( "lcdsmall" ); + + // Load the user interface + String_Init(); + CG_AssetCache(); + CG_LoadHudMenu(); + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + CG_TransitionPermanent(); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) + { + Com_Error( ERR_FATAL, "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + CG_ParseGametypeFile ( ); + + // load the new map + CG_LoadingString( "collision map" ); + + CG_LoadingStage ( 1 ); + + trap_CM_LoadMap( cgs.mapname, qfalse ); + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_LoadingStage ( 2 ); + + // force players to load instead of defer + cg.loading = qtrue; + + CG_LoadingString( "sounds" ); + + CG_RegisterLadders ( ); + CG_RegisterSounds(); + CG_InitHitModel( ); + + CG_LoadingString( "graphics" ); + BG_ParseInviewFile ( ); + CG_RegisterGraphics ( ); + +// CG_RegisterMission ( ); + + CG_ParseGore(); + + CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + CG_LoadingStage ( 14 ); + + CG_InitLocalEntities(); + + CG_LoadingStage ( 15 ); + + CG_StartMusic(qfalse); + + CG_LoadingString( "Clearing light styles" ); + CG_ClearLightStyles(); + + CG_LoadingString( "" ); + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds( qtrue ); + + // In a mission game we want to bring up the objective screen on initial connection + if ( cgs.gametypeData->description || cgs.pickupsDisabled || cgs.gametypeData->teams ) + { + cg.popupObjectives = qtrue; + } + + trap_Cvar_Set ( "ui_info_gametype", va("%i",cgs.gametype ) ); + trap_Cvar_Set ( "ui_info_objectives", cgs.gametypeData->description ); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + cg.loading = qfalse; // future players will be deferred +} + +void CG_ShutdownG2Models(void) +{ + int i, j; + clientInfo_t *ci; + + for(i=0;ighoul2Model) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + } +} + +void CG_ShutdownGore(void); + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) +{ + trap_FX_FreeSystem(); + + CG_ShutDownWeapons(); + CG_ShutdownG2Models(); + + CG_ShutdownGore(); + + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} diff --git a/code/cgame/cg_media.h b/code/cgame/cg_media.h new file mode 100644 index 0000000..e69de29 diff --git a/code/cgame/cg_miscents.c b/code/cgame/cg_miscents.c new file mode 100644 index 0000000..0322bd0 --- /dev/null +++ b/code/cgame/cg_miscents.c @@ -0,0 +1,77 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_miscents.h -- + +#include "cg_local.h" + +#define MAX_MISC_ENTS 4000 + +static refEntity_t *MiscEnts = 0; +static float *Radius = 0; +static int NumMiscEnts = 0; + +void CG_MiscEnt(void) +{ + int modelIndex; + refEntity_t *RefEnt; + TCGMiscEnt *data = (TCGMiscEnt *)cg.sharedBuffer; + vec3_t mins, maxs; + float *radius; + + if (NumMiscEnts >= MAX_MISC_ENTS) + { + return; + } + + if (!MiscEnts) + { + MiscEnts = (refEntity_t *)trap_VM_LocalAlloc(sizeof(refEntity_t)*MAX_MISC_ENTS); + Radius = (float *)trap_VM_LocalAlloc(sizeof(float)*MAX_MISC_ENTS); + } + + radius = &Radius[NumMiscEnts]; + RefEnt = &MiscEnts[NumMiscEnts++]; + + modelIndex = trap_R_RegisterModel(data->mModel); + if (modelIndex == 0) + { + Com_Error(ERR_DROP, "client_model has invalid model definition"); + return; + } + + memset(RefEnt, 0, sizeof(refEntity_t)); + RefEnt->reType = RT_MODEL; + RefEnt->hModel = modelIndex; + RefEnt->frame = 0; + trap_R_ModelBounds(modelIndex, mins, maxs); + VectorCopy(data->mScale, RefEnt->modelScale); + VectorCopy(data->mOrigin, RefEnt->origin); + + VectorScaleVector(mins, data->mScale, mins); + VectorScaleVector(maxs, data->mScale, maxs); + *radius = Distance(mins, maxs); + + AnglesToAxis( data->mAngles, RefEnt->axis ); + CG_ScaleModelAxis(RefEnt); +} + +void CG_DrawMiscEnts(void) +{ + int i; + refEntity_t *RefEnt; + float *radius; + vec3_t difference; + + RefEnt = MiscEnts; + radius = Radius; + for(i=0;iorigin, cg.refdef.vieworg, difference); + if (VectorLength(difference)-(*radius) <= RMG_distancecull.value) + { + trap_R_AddRefEntityToScene(RefEnt); + } + RefEnt++; + radius++; + } +} diff --git a/code/cgame/cg_newDraw.c b/code/cgame/cg_newDraw.c new file mode 100644 index 0000000..6f8840e --- /dev/null +++ b/code/cgame/cg_newDraw.c @@ -0,0 +1,1119 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_newdraw.c -- + +#include "cg_local.h" +#include "../game/bg_public.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + +static float healthColors[4][4] = +{ + { 1.0f, 0.69f, 0.0f, 1.0f } , // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } // health > 100 +}; + +static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) +{ + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + const char *s; + + if (!ci) + { + return; + } + + s = CG_ConfigString(CS_LOCATIONS + ci->location); + + if (!s || !*s) + { + s = "unknown"; + } + + CG_DrawText(rect->x, rect->y + rect->h, cgs.media.hudFont, scale, color, s, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerSniperBullet +//============================================================================== +static void CG_DrawPlayerSniperBullet ( rectDef_t* rect, qhandle_t shader, int bullet ) +{ + // Dont draw the bullet if the player doesnt have enough ammo + if ( bullet > cg.predictedPlayerState.clip[ATTACK_NORMAL][WP_MSG90A1] ) + { + return; + } + + CG_DrawStretchPic ( rect->x, rect->y, rect->w, rect->h, 0, 0, 1, 1, NULL, shader ); +} + +//============================================================================== +// CG_DrawPlayerSniperMagnification +//============================================================================== +static void CG_DrawPlayerSniperMagnification ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + const char* mag; + + switch ( cg.predictedPlayerState.zoomFov ) + { + default: + case 20: mag = "5x"; break; + case 10: mag = "10x"; break; + case 5: mag = "20x"; break; + } + + // Center the text + CG_DrawText (rect->x, rect->y, font, scale, color, mag, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerWeaponName +//============================================================================== +static void CG_DrawPlayerWeaponName ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + float width; + const char* name; + + if( cg.weaponSelect > WP_NONE ) + { + // Equipped weapon + name = weaponData[cg.weaponSelect].classname; + } + else + { + // No weapon equipped. + name = "NONE"; + } + + // Get the width of the text so we can center it + width = trap_R_GetTextWidth ( name, font, scale, 0 ); + + // Center the text + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, name, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerWeaponName +//============================================================================== +static void CG_DrawPlayerAltWeaponName ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + float width; + const char* name; + + if( cg.weaponSelect > WP_NONE ) + { + // Equipped weapon + name = weaponData[cg.weaponSelect].attack[ATTACK_ALTERNATE].name; + } + else + { + // No weapon equipped. + name = "NONE"; + } + + // Get the width of the text so we can center it + width = trap_R_GetTextWidth ( name, font, scale, 0 ); + + // Center the text + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, name, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerWeaponAmmo +//============================================================================== +static void CG_DrawPlayerWeaponAmmo(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + const char *ammo; + int value; + float width; + + // If the client doenst have a selected weapon then dont draw the ammo + if ( cg.weaponSelect <= WP_NONE ) + return; + + // Retrieve the ammo value + value = cg.predictedPlayerState.ammo[weaponData[cg.weaponSelect].attack[ATTACK_NORMAL].ammoIndex]; + + // If the ammo value is crazy then dont display it + if ( value <= -1 ) + return; + + // Build the ammo string padded with zeros + ammo = va ( "%03d", value ); + + // Retrive the width of the ammo string so it can be centered + width = trap_R_GetTextWidth (ammo, font, scale, 0 ); + + // Draw the ammo string centered + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, ammo, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerAltWeaponAmmo +//============================================================================== +static void CG_DrawPlayerAltWeaponAmmo(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + const char *ammo; + int value; + float width; + + // If the client doenst have a selected weapon then dont draw the ammo + if ( cg.weaponSelect <= WP_NONE ) + return; + + // If the selected weapon doesn't have an altclip, don't draw. + if(weaponData[cg.weaponSelect].attack[ATTACK_ALTERNATE].clipSize<=0) + return; + + // Retrieve the ammo value + value = cg.predictedPlayerState.ammo[weaponData[cg.weaponSelect].attack[ATTACK_ALTERNATE].ammoIndex]; + value += cg.predictedPlayerState.clip[ATTACK_ALTERNATE][cg.weaponSelect]; + + // If the ammo value is crazy then dont display it + if ( value <= -1 ) + return; + + // Build the ammo string padded with zeros + ammo = va ( "%03d", value ); + + // Retrive the width of the ammo string so it can be centered + width = trap_R_GetTextWidth (ammo, font, scale, 0 ); + + // Draw the ammo string centered + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, ammo, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerAltWeaponAmmoIcon +//============================================================================== +static void CG_DrawPlayerAltWeaponAmmoIcon(rectDef_t *rect, vec4_t color ) +{ + vec4_t fade; + + // If the client doenst have a selected weapon then dont draw the ammo icon + if ( cg.weaponSelect <= WP_NONE ) + return; + + // If there is no loaded icon then dont draw it + if ( !cg_weapons[cg.weaponSelect].attack[ATTACK_ALTERNATE].ammoIcon ) + { + return; + } + + // Fade out the icon if there isnt anything in the alt clip + fade[0] = color[0]; + fade[1] = color[1]; + fade[2] = color[2]; + fade[3] = color[3] * (cg.predictedPlayerState.clip[ATTACK_ALTERNATE][cg.weaponSelect]?1:0.20f); + + trap_R_SetColor ( fade ); + + // Draw the icon + CG_DrawPic ( rect->x, rect->y, rect->w, rect->h, cg_weapons[cg.weaponSelect].attack[ATTACK_ALTERNATE].ammoIcon ); + + // Back to no color + trap_R_SetColor ( NULL ); +} + +//============================================================================== +// CG_DrawPlayerWeaponClip +//============================================================================== +static void CG_DrawPlayerWeaponClip (rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + const char *clip; + int value; + float width; + + // If the client doenst have a selected weapon then dont draw the ammo + if ( cg.weaponSelect <= WP_NONE ) + return; + + // Retrieve the ammo value + value = cg.predictedPlayerState.clip[ATTACK_NORMAL][cg.weaponSelect]; + + // If the ammo value is crazy then dont display it + if ( value <= -1 ) + return; + + // Build the clip string padded with zeros + clip = va ( "%02d", value ); + + // Retrive the width of the clip string so it can be centered + width = trap_R_GetTextWidth (clip, font, scale, 0 ); + + // Draw the clip string centered + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, clip, 0, 0 ); +} + +//============================================================================== +// CG_DrawPlayerWeaponMode +//============================================================================== +static void CG_DrawPlayerWeaponMode ( rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + float width; + const char* mode; + int curMode; + + // Use the current fire mode for this weapon, if its undefined then find what it will + // be once its set + curMode = cg.predictedPlayerState.firemode[cg.weaponSelect]; + if ( curMode == WP_FIREMODE_NONE ) + { + curMode = BG_FindFireMode ( cg.weaponSelect, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + } + + // TODO: Retrieve the real mode + switch ( curMode ) + { + case WP_FIREMODE_NONE: + mode = ""; + break; + case WP_FIREMODE_AUTO: + mode = "AUTO"; + break; + case WP_FIREMODE_BURST: + mode = "BURST"; + break; + case WP_FIREMODE_SINGLE: + mode = "SINGLE"; + break; + default: + // This shouldn't happen. + assert(0); + mode = ""; + } + + // Get the width of the text so we can center it + width = trap_R_GetTextWidth ( mode, font, scale, 0 ); + + // Center the text + CG_DrawText (rect->x + (rect->w - width) / 2, rect->y + rect->h, font, scale, color, mode, 0, 0 ); +} + +//============================================================================== +// CG_DrawUseIcon +//============================================================================== +void CG_DrawUseIcon ( rectDef_t* rect ) +{ + // If not in a use zone then dont bother + if ( !(cg.predictedPlayerState.pm_flags & PMF_CAN_USE ) ) + { + return; + } + + CG_DrawStretchPic ( rect->x, rect->y, rect->w, rect->h,0, 0, 1, 1, NULL, + cgs.gameIcons [ cg.predictedPlayerState.generic1 ] ); +} + +//============================================================================== +// CG_DrawPlayerGametypeItems +//============================================================================== +void CG_DrawPlayerGametypeItems ( rectDef_t* rect ) +{ + float x; + int i; + + x = rect->x; + + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + // Doesnt have this mission item + if ( !(cg.predictedPlayerState.stats[STAT_GAMETYPE_ITEMS] & (1<y, rect->w, rect->h, cg_items[i+MODELINDEX_GAMETYPE_ITEM].icon ); + + x += 2; + } +} + +//============================================================================== +// CG_DrawPlayerArmor +//============================================================================== +void CG_DrawPlayerArmor ( rectDef_t* rect ) +{ + float x; + float w; + + // Draw the icon + trap_R_SetColor ( NULL ); + + w = rect->w; + x = rect->x; + + for ( ; w >= rect->h; w -= rect->h, x += rect->h ) + { + CG_DrawPic ( x, rect->y - rect->h, rect->h, rect->h, cgs.media.armorShader ); + } + + if ( w ) + { + CG_DrawStretchPic ( x, rect->y - rect->h, w, rect->h, + 0, 0, w/rect->h, 1.0f, NULL, cgs.media.armorShader ); + } +} + + +static void CG_DrawPlayerScore( rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + const char* s; + + s = va("%03d",cg.snap->ps.persistant[PERS_SCORE]); + + CG_DrawText(rect->x + (rect->w - trap_R_GetTextWidth(s,font,scale,0)) / 2, rect->y + rect->h, font, scale, color, s, 0, 0 ); +} + +static void CG_DrawRedScore(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + int value; + char num[16]; + + if ( cgs.scores1 == SCORE_NOT_PRESENT ) + { + Com_sprintf (num, sizeof(num), "-"); + } + else + { + Com_sprintf (num, sizeof(num), "%i", cgs.scores1); + } + + value = trap_R_GetTextWidth (num, font, scale, 0); + + CG_DrawText (rect->x + rect->w - value, rect->y + rect->h, font, scale, color, num, 0, 0 ); +} + +static void CG_DrawBlueScore(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + int value; + char num[16]; + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) + { + Com_sprintf (num, sizeof(num), "-"); + } + else + { + Com_sprintf (num, sizeof(num), "%i", cgs.scores2); + } + + value = trap_R_GetTextWidth(num, font, scale, 0 ); + CG_DrawText(rect->x + rect->w - value, rect->y + rect->h, font, scale, color, num, 0, 0 ); +} + +#define MSECS_PER_FRAME (1000.0 / 60.0) +#define HUD_FADE_SPEED 0.05 + +float TimeAdjustHUDVal(float mainVal, float dstVal, float factor) +{ + mainVal = mainVal * (1.0 - factor) + dstVal * factor; + + //once we get close enough, just finish up + if(fabs(mainVal - dstVal) < .01) + { // if we don't have this, the last few pixels take FOREVER + mainVal = dstVal; + } + + return mainVal; +} + +void HandleHUDSegment(float *floorVal, float *ceilVal, float dstVal, float factor) +{ + if(dstVal < *floorVal) + { + *floorVal = dstVal; + } + else if(dstVal > *floorVal) + { + *floorVal = TimeAdjustHUDVal(*floorVal, dstVal, factor); + } + + if(dstVal > *ceilVal) + { + *ceilVal = dstVal; + } + else if(dstVal < *ceilVal) + { + *ceilVal = TimeAdjustHUDVal(*ceilVal, dstVal, factor); + } +} + +float CG_GetValue(int ownerDraw) +{ + centity_t *cent; + playerState_t *ps; + + cent = CG_GetEntity ( cg.snap->ps.clientNum ); + ps = &cg.snap->ps; + + switch (ownerDraw) + { + case CG_PLAYER_WEAPON_AMMO: + if ( cent->currentState.weapon ) + { + return ps->ammo[weaponData[cent->currentState.weapon].attack[ATTACK_NORMAL].ammoIndex]; + } + break; + + case CG_PLAYER_SCORE: + return cg.snap->ps.persistant[PERS_SCORE]; + + case CG_PLAYER_HEALTH: + return ((float) ps->stats[STAT_HEALTH] * 0.01f) + 0.01f; + + case CG_PLAYER_HEALTH_FADE: + { + static float healthEdge = 0.0; + static float healthFull = 0.0; + + float curHealth = ps->stats[STAT_HEALTH] * 0.01; + float adjustFactor = (cg.frametime / MSECS_PER_FRAME) * HUD_FADE_SPEED; + + HandleHUDSegment(&healthEdge, &healthFull, curHealth, adjustFactor); + + if ( healthFull < 0 ) + healthFull = 0; + + return healthFull; + } + + case CG_PLAYER_ARMOR_FADE: + { + static float armorEdge = 0.0; + static float armorFull = 0.0; + + float curArmor = ps->stats[STAT_ARMOR] * 0.01; + float adjustFactor = (cg.frametime / MSECS_PER_FRAME) * HUD_FADE_SPEED; + + HandleHUDSegment(&armorEdge, &armorFull, curArmor, adjustFactor); + + if ( armorFull < 0 ) + armorFull = 0; + + return armorFull; + } + + case CG_PLAYER_ARMOR: + return ((float) ps->stats[STAT_ARMOR] * 0.01f);; + + case CG_RED_SCORE: + return cgs.scores1; + break; + + case CG_BLUE_SCORE: + return cgs.scores2; + break; + + default: + break; + } + + return -1; +} + +qboolean CG_OwnerDrawDisabled(int flags, const char* param ) +{ + return qfalse; +} + +qboolean CG_OwnerDrawVisible(int flags, const char* param ) +{ + qboolean visible = qtrue; + + while ( visible ) + { + // Player on the red team? + if ( flags & CG_SHOW_ONREDTEAM ) + { + visible = cg.predictedPlayerState.persistant[PERS_TEAM]==TEAM_RED?qtrue:qfalse; + flags &= ~(CG_SHOW_ONREDTEAM); + continue; + } + // Player on the blue team? + else if ( flags & CG_SHOW_ONBLUETEAM ) + { + visible = cg.predictedPlayerState.persistant[PERS_TEAM]==TEAM_BLUE?qtrue:qfalse; + flags &= ~(CG_SHOW_ONBLUETEAM); + } + else if ( flags & CG_SHOW_HUD_NIGHTVISION ) + { + visible = ((cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON) && cg.predictedPlayerState.stats[STAT_GOGGLES] == GOGGLES_NIGHTVISION && !(cg.predictedPlayerState.pm_flags&PMF_ZOOMED))? qtrue : qfalse; + flags &= ~(CG_SHOW_HUD_NIGHTVISION); + } + else if ( flags & CG_SHOW_HUD_THERMAL ) + { + visible = ((cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON) && cg.predictedPlayerState.stats[STAT_GOGGLES] == GOGGLES_INFRARED )? qtrue : qfalse; + flags &= ~(CG_SHOW_HUD_THERMAL); + } + // Only draw the sniper scope when zoomed + else if ( flags & CG_SHOW_HUD_SNIPERSCOPE ) + { + visible = (cg.predictedPlayerState.pm_flags&PMF_ZOOMED)?qtrue:qfalse; + flags &= ~(CG_SHOW_HUD_SNIPERSCOPE); + } + // Draw the health as long as we arent zoomed + else if ( flags & CG_SHOW_HUD_HEALTH ) + { + visible = cg.showScores ? qfalse : qtrue; + flags &= ~(CG_SHOW_HUD_HEALTH); + } + // Timer + else if ( flags & CG_SHOW_HUD_TIMER ) + { + if ( cg.showScores ) + { + visible = qfalse; + } + else if ( !cg_drawTimer.integer ) + { + visible = qfalse; + } + + flags &= ~(CG_SHOW_HUD_TIMER); + } + // Should the alternate weapon information be shown? + else if (flags & (CG_SHOW_PLAYER_ALT_WEAPONINFO|CG_HIDE_PLAYER_ALT_WEAPONINFO) ) + { + if ( cg.predictedPlayerState.pm_flags & PMF_ZOOMED ) + { + visible = qfalse; + } + + if ( cg.showScores ) + { + visible = qfalse; + } + else + { + if ( !BG_WeaponHasAlternateAmmo ( cg.weaponSelect ) ) + { + visible = qfalse; + } + + // Dont show it if we dont have ammo + if ( !cg.predictedPlayerState.ammo[ weaponData[cg.weaponSelect].attack[ATTACK_ALTERNATE].ammoIndex ] && + !cg.predictedPlayerState.clip[ATTACK_ALTERNATE][ cg.weaponSelect ] ) + { + visible = qfalse; + } + + // INvert the visible flag for hidden + if ( flags & CG_HIDE_PLAYER_ALT_WEAPONINFO ) + { + if ( cg.predictedPlayerState.pm_flags & PMF_ZOOMED ) + { + visible = qfalse; + } + else + { + visible = !visible; + } + } + } + + flags &= ~(CG_SHOW_PLAYER_ALT_WEAPONINFO|CG_HIDE_PLAYER_ALT_WEAPONINFO); + } + // Draw the weapon info when we arent zoomed + else if ( flags & CG_SHOW_HUD_WEAPONINFO ) + { + visible = (cg.showScores||(cg.predictedPlayerState.pm_flags&PMF_ZOOMED))?qfalse:qtrue; + flags &= ~(CG_SHOW_HUD_WEAPONINFO); + } + else if (flags & CG_SHOW_ANYTEAMGAME) + { + if( !cgs.gametypeData->teams ) + { + visible = qfalse; + } + + flags &= ~(CG_SHOW_ANYTEAMGAME); + } + else if (flags & CG_SHOW_ANYNONTEAMGAME) + { + if( cgs.gametypeData->teams ) + { + visible = qfalse; + } + + flags &= ~(CG_SHOW_ANYNONTEAMGAME); + } + else + { + break; + } + } + + return visible; +} + +static void CG_Draw1stPlace(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + if (cgs.scores1 != SCORE_NOT_PRESENT) + { + CG_DrawText(rect->x, rect->y, font, scale, color, va("%2i", cgs.scores1),0, 0 ); + } +} + +static void CG_Draw2ndPlace(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + if (cgs.scores2 != SCORE_NOT_PRESENT) + { + CG_DrawText(rect->x, rect->y, font, scale, color, va("%2i", cgs.scores2),0, 0 ); + } +} + +// Draw an entire row of weapons for the visual weapon selection menu + +static void CG_DrawWeaponList(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + static vec4_t colorNormal = { 0.5f, 0.5f, 0.5f, 0.8f }; + static vec4_t colorShadow = { 0.0f, 0.0f, 0.0f, 0.25f }; + static vec4_t colorSelected = { 204.0f / 255.0f, 222.0f / 255.0f, 159.0f / 255.0f, 0.925f }; + static vec4_t colorDisabled = { 0.4f, 0.4f, 0.4f, 0.8f }; + + centity_t *cent; + playerState_t *ps; + qhandle_t icon; + int weaponToDraw; + int x,y; + int w,h; + int i,wid; + int category, activeCategory; + char numStr[3]; + int category_x[CAT_MAX]; + gitem_t *item; + const char *s; + + + vec4_t colorDraw; + + rect->x = 20; + rect->y = 65; + rect->h = 320; + + // are we showing weapon menu? + if (!cg.weaponMenuUp) + { + return; + } + + cent = CG_GetEntity ( cg.snap->ps.clientNum ); + ps = &cg.snap->ps; + + // If the menu was up during follow then turn it off + if ( (ps->pm_flags & PMF_FOLLOW) || ps->pm_type == PM_SPECTATOR ) + { + cg.weaponMenuUp = qfalse; + return; + } + + + h = rect->h / CAT_MAX; + w = h*2; // we know that the weapon icons are a 2:1 aspect ratio + + activeCategory = -1; + + // start at left edge for all weapon categories + for (i=CAT_NONE; ix + w; + } + + for (weaponToDraw = WP_NONE + 1; weaponToDraw < WP_NUM_WEAPONS; weaponToDraw++ ) + { + category = weaponData[weaponToDraw].category; + + x = category_x[category]; + y = rect->y + (category-1) * h; + + // found a weapon in this catagory + if (weaponToDraw == cg.weaponMenuSelect) + { + // this is the currently selected weapon, so draw in green + Vector4Copy(colorSelected, colorDraw); + activeCategory = category; + } + else if ( (ps->stats[STAT_WEAPONS] & ( 1 << weaponToDraw )) ) + { + int ammoCount = 0; + + ammoCount += ps->ammo[weaponData[weaponToDraw].attack[ATTACK_NORMAL].ammoIndex]; + ammoCount += ps->clip[ATTACK_NORMAL][weaponToDraw]; + ammoCount += ps->ammo[weaponData[weaponToDraw].attack[ATTACK_ALTERNATE].ammoIndex]; + ammoCount += ps->clip[ATTACK_ALTERNATE][weaponToDraw]; + + if ( !ammoCount ) + { + if (CAT_GRENADE <= category) + { + // no grenades means no grenades + continue; + } + else + { + // weapon has no ammo so show it disabled + Vector4Copy(colorDisabled, colorDraw ); + } + } + else + { + // draw in normal color + Vector4Copy(colorNormal, colorDraw); + } + } + else + { // don't have it, so don't draw + continue; + } + + icon = cg_weapons[ weaponToDraw ].weaponIcon; + if (icon) + { + // draw weapon icon + if (CAT_GRENADE <= category) + { + // draw square icons + trap_R_SetColor( colorShadow ); + CG_DrawPic( x+2, y+2, h * 0.90f, h * 0.90f, icon ); + + trap_R_SetColor( colorDraw ); + CG_DrawPic( x, y, h * 0.90f, h * 0.90f, icon ); + + category_x[category] += h; + } + else + { + // draw rectangular icons + trap_R_SetColor( colorShadow ); + CG_DrawPic( x+2, y+2, w * 0.90f, h * 0.90f, icon ); + + trap_R_SetColor( colorDraw ); + CG_DrawPic( x, y, w * 0.90f, h * 0.90f, icon ); + + category_x[category] += w; + } + } + + trap_R_SetColor( NULL ); + } + + // show weapon catagory numbers + for (i = CAT_NONE + 1; i < CAT_MAX; i++) + { + y = rect->y + (i - 1) * h; + Com_sprintf(numStr, sizeof(numStr),"%d", i ); + wid = trap_R_GetTextWidth (numStr, font, 1.25f, 0 ); + if (i == activeCategory) + { + Vector4Copy(colorSelected, colorDraw); + } + else + { + Vector4Copy(colorNormal, colorDraw); + } + + colorDraw[3] = 1.0f; + + CG_DrawText (rect->x + (w - wid) / 2 + 2, y, font, 0.6f, colorDraw, numStr, 0, DT_DROPSHADOW ); + } + + item = BG_FindWeaponItem ( cg.weaponMenuSelect ); + if ( item ) + { + // Draw the selected weapons name + s = va("Weapon: %s", item->pickup_name); + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ); + CG_DrawText ( 640 - rect->x - w, rect->y, cgs.media.hudFont, 0.43f, colorSelected, s, 0, 0 ); + + // Draw the selected weapons ammo count + if ( BG_WeaponHasAlternateAmmo ( cg.weaponMenuSelect ) ) + { + s = va("Ammo: %i / %i", cg.predictedPlayerState.clip[ATTACK_NORMAL][cg.weaponMenuSelect] + cg.predictedPlayerState.ammo[weaponData[cg.weaponMenuSelect].attack[ATTACK_NORMAL].ammoIndex], + cg.predictedPlayerState.clip[ATTACK_ALTERNATE][cg.weaponMenuSelect] + cg.predictedPlayerState.ammo[weaponData[cg.weaponMenuSelect].attack[ATTACK_ALTERNATE].ammoIndex]); + } + else + { + s = va("Ammo: %i", cg.predictedPlayerState.clip[ATTACK_NORMAL][cg.weaponMenuSelect] + cg.predictedPlayerState.ammo[weaponData[cg.weaponMenuSelect].attack[ATTACK_NORMAL].ammoIndex]); + } + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ); + CG_DrawText ( 640 - rect->x - w, rect->y + 15, cgs.media.hudFont, 0.43f, colorSelected, s, 0, 0 ); + } +} + +const char *CG_GetGameStatusText() +{ + const char *s = ""; + + if ( !cgs.gametypeData->teams ) + { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); + } + } + else + { + if ( cg.teamScores[0] == cg.teamScores[1] ) + { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } + else if ( cg.teamScores[0] >= cg.teamScores[1] ) + { + s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); + } + else + { + s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); + } + } + + return s; +} + +static void CG_DrawGameStatus(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + CG_DrawText (rect->x, rect->y + rect->h, font, scale, color, CG_GetGameStatusText(), 0 , 0 ); +} + +const char *CG_GameTypeString() +{ + return cgs.gametypeData->displayName; +} + +static void CG_DrawGameType(rectDef_t *rect, qhandle_t font, float scale, vec4_t color ) +{ + CG_DrawText (rect->x, rect->y + rect->h, font, scale, color, CG_GameTypeString(), 0, 0 ); +} + +void CG_OwnerDraw ( float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, qhandle_t font, float scale, vec4_t color, qhandle_t shader, int textStyle, const char* param ) +{ + rectDef_t rect; + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) + { + case CG_USE_ICON: + CG_DrawUseIcon ( &rect ); + break; + + case CG_PLAYER_GAMETYPE_ITEMS: + CG_DrawPlayerGametypeItems ( &rect ); + break; + + case CG_PLAYER_ARMOR: + CG_DrawPlayerArmor ( &rect ); + break; + + case CG_PLAYER_WEAPON_AMMO: + CG_DrawPlayerWeaponAmmo(&rect, font, scale, color ); + break; + + case CG_PLAYER_WEAPON_CLIP: + CG_DrawPlayerWeaponClip(&rect, font, scale, color ); + break; + + case CG_PLAYER_WEAPON_NAME: + CG_DrawPlayerWeaponName(&rect, font, scale, color ); + break; + + case CG_PLAYER_ALT_WEAPON_NAME: + CG_DrawPlayerAltWeaponName(&rect, font, scale, color ); + break; + + case CG_PLAYER_ALT_WEAPON_AMMO: + CG_DrawPlayerAltWeaponAmmo(&rect, font, scale, color ); + break; + + case CG_PLAYER_ALT_WEAPON_AMMOICON: + CG_DrawPlayerAltWeaponAmmoIcon ( &rect, color ); + break; + + case CG_PLAYER_WEAPON_MODE: + CG_DrawPlayerWeaponMode(&rect, font, scale, color ); + break; + + case CG_PLAYER_SCORE: + CG_DrawPlayerScore(&rect, font, scale, color ); + break; + + case CG_GAME_TYPE: + CG_DrawGameType(&rect, font, scale, color ); + break; + + case CG_RED_SCORE: + CG_DrawRedScore(&rect, font, scale, color ); + break; + + case CG_BLUE_SCORE: + CG_DrawBlueScore(&rect, font, scale, color ); + break; + + case CG_GAME_STATUS: + CG_DrawGameStatus(&rect, font, scale, color ); + break; + + case CG_1STPLACE: + CG_Draw1stPlace(&rect, font, scale, color ); + break; + + case CG_2NDPLACE: + CG_Draw2ndPlace(&rect, font, scale, color ); + break; + + case CG_WEAPON_LIST: + CG_DrawWeaponList(&rect, font, scale, color ); + break; + + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + + case CG_PLAYER_SNIPER_BULLET_1: + case CG_PLAYER_SNIPER_BULLET_2: + case CG_PLAYER_SNIPER_BULLET_3: + case CG_PLAYER_SNIPER_BULLET_4: + case CG_PLAYER_SNIPER_BULLET_5: + case CG_PLAYER_SNIPER_BULLET_6: + CG_DrawPlayerSniperBullet ( &rect, shader, ownerDraw - CG_PLAYER_SNIPER_BULLET_1 + 1); + break; + + case CG_PLAYER_SNIPER_MAGNIFICATION: + CG_DrawPlayerSniperMagnification ( &rect, font, scale, color ); + break; + + default: + break; + } +} + +void CG_MouseEvent(int x, int y) +{ + int n; + + if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) + { + trap_Key_SetCatcher(0); + return; + } + + cgs.cursorX+= x; + if (cgs.cursorX < 0) + { + cgs.cursorX = 0; + } + else if (cgs.cursorX > 640) + { + cgs.cursorX = 640; + } + + cgs.cursorY += y; + if (cgs.cursorY < 0) + { + cgs.cursorY = 0; + } + else if (cgs.cursorY > 480) + { + cgs.cursorY = 480; + } + + n = Display_CursorType(cgs.cursorX, cgs.cursorY); + cgs.activeCursor = 0; + if (n == CURSOR_ARROW) + { + cgs.activeCursor = cgs.media.cursor; + } + else if (n == CURSOR_SIZER) + { + cgs.activeCursor = cgs.media.cursor; + } + + if (cgs.capturedItem) + { + Display_MouseMove(cgs.capturedItem, x, y); + } + else + { + Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); + } +} + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling(int type) +{ + cgs.eventHandling = type; +} + + +void CG_KeyEvent(int key, qboolean down) +{ + if (!down) + { + return; + } + + if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) + { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + return; + } + + Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); + + if (cgs.capturedItem) + { + cgs.capturedItem = NULL; + } + else + { + if (key == K_MOUSE2 && down) + { + cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); + } + } +} + +void CG_RunMenuScript(const char **args) +{ +} + + +void CG_GetTeamColor(vec4_t *color) +{ + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { + (*color)[0] = 1.0f; + (*color)[3] = 0.25f; + (*color)[1] = (*color)[2] = 0.0f; + } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { + (*color)[0] = (*color)[1] = 0.0f; + (*color)[2] = 1.0f; + (*color)[3] = 0.25f; + } else { + (*color)[0] = (*color)[2] = 0.0f; + (*color)[1] = 0.17f; + (*color)[3] = 0.25f; + } +} + diff --git a/code/cgame/cg_playeranim.c b/code/cgame/cg_playeranim.c new file mode 100644 index 0000000..c3678e0 --- /dev/null +++ b/code/cgame/cg_playeranim.c @@ -0,0 +1,119 @@ +#include "cg_local.h" +#include "animtable.h" + + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation counts and rates +models/players/visor/animation.cfg, etc +====================== +*/ +qboolean CG_ParseAnimationFile ( const char *filename, clientInfo_t *ci) +{ + const char *text_p; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + int animNum; + animation_t *animations; + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 || len >= sizeof( text ) - 1 ) + { + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quiet the compiler warning + + //FIXME: have some way of playing anims backwards... negative numFrames? + + //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 + for(i = 0; i < MAX_ANIMATIONS; i++) + { + animations[i].firstFrame = 0; + animations[i].numFrames = 0; + animations[i].loopFrames = -1; + animations[i].frameLerp = 100; + animations[i].initialLerp = 100; + } + + // read information for each frame + while(1) + { + token = COM_Parse( &text_p ); + + if ( !token || !token[0]) + { + break; + } + + animNum = GetIDForString(animTable, token); + if(animNum == -1) + { +//#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); +#endif + continue; + } + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animations[animNum].frameLerp = floor(1000.0f / fps); + } + else + { + animations[animNum].frameLerp = ceil(1000.0f / fps); + } + + animations[animNum].initialLerp = ceil(1000.0f / fabs(fps)); + } + + return qtrue; +} diff --git a/code/cgame/cg_playeranimate.c b/code/cgame/cg_playeranimate.c new file mode 100644 index 0000000..e69de29 diff --git a/code/cgame/cg_players.c b/code/cgame/cg_players.c new file mode 100644 index 0000000..600dbd0 --- /dev/null +++ b/code/cgame/cg_players.c @@ -0,0 +1,1472 @@ +// Copyright (C) 2001-2002 Raven Software, Inc. +// +// cg_players.c -- handle the media and animation for player entities + +#include "cg_local.h" +#include "..\ghoul2\g2.h" +#include "..\game\inv.h" + +typedef struct SCustomSound +{ + const char *mGroup; + const int mIndex; +} TCustomSound; + +TCustomSound CustomSounds[MAX_CUSTOM_SOUNDS] = +{ + { "Death", 0 }, + { "Death", 1 }, + { "Death", 2 }, + { "Pain", 0 }, + { "Pain", 1 }, + { "Pain", 2 }, +}; + +/* +================ +CG_CustomSound +================ +*/ +sfxHandle_t CG_CustomSound(int clientNum, const char *soundName) +{ + return trap_S_RegisterSound( soundName ); +} + +/* +================ +CG_CustomPlayerSound +================ +*/ +sfxHandle_t CG_CustomPlayerSound(int clientNum, ECustomSounds sound) +{ + clientInfo_t *ci; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) + { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + return ci->sounds[sound]; +} + + +/* +========================== +CG_RegisterClientIdentity +========================== +*/ +static qboolean CG_RegisterClientIdentity ( clientInfo_t *ci, const char *identityName ) +{ + char afilename[MAX_QPATH]; + TIdentity* identity; + + // Find the identity in question + identity = BG_FindIdentity( identityName ); + if (!identity ) + { + return qfalse; + } + + // Register the identity + ci->ghoul2Model = CG_RegisterIdentity( identity, afilename, &ci->gender ); + if (!ci->ghoul2Model) + { + return qfalse; + } + + // find the bolt point for hanging world models of weapons on + ci->boltWorldWeapon = trap_G2API_AddBolt(ci->ghoul2Model, 0, "rhang_tag_bone"); + + if ( !BG_ParseAnimationFile( afilename, ci->animations ) ) + { + Com_Printf( "Failed to load animation file %s\n", afilename ); + trap_G2API_RemoveGhoul2Model(&ci->ghoul2Model, 0); + return qfalse; + } + + // Flag model as male or female. + assert(afilename); + ci->isMale=(strstr(afilename,"female"))?qfalse:qtrue; + ci->identity = identity; + + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) +{ + int i; + int modelloaded; + int clientNum; + + modelloaded = qtrue; + + // Initialize all bolt positions + ci->boltWorldWeapon = -1; + ci->boltNightvision = -1; + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + ci->boltGametypeItems[i] = -1; + } + + // Register the client identity + if ( !CG_RegisterClientIdentity ( ci, ci->identityName ) ) + { + if ( !CG_RegisterClientIdentity ( ci, DEFAULT_IDENTITY ) ) + { + Com_Error( ERR_FATAL, "DEFAULT_IDENTITY (%s) failed to register", DEFAULT_IDENTITY ); + } + } + + // sounds + for( i=0; i< MAX_CUSTOM_SOUNDS; i++ ) + { + const char* sound; + + ci->sounds[i] = 0; + sound = NULL; + + sound = BG_GetModelSound ( ci->identityName, CustomSounds[i].mGroup, CustomSounds[i].mIndex ); + + // If there is a valid sound to load then load it + if ( sound && sound[0] ) + { + ci->sounds[i] = trap_S_RegisterSound( sound ); + } + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + centity_t* cent; + + cent = CG_GetEntity ( i ); + + if ( cent->currentState.clientNum == clientNum && + cent->currentState.eType == ET_PLAYER ) + { + CG_ResetPlayerEntity( cent ); + } + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) +{ + int i; + + to->gender = from->gender; + + to->boltWorldWeapon = from->boltWorldWeapon; + + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + to->boltGametypeItems[i] = from->boltGametypeItems[i]; + } + + to->boltNightvision = from->boltNightvision; + + // Copy all the ghoul2 information too + if (from->ghoul2Model) + { + trap_G2API_DuplicateGhoul2Instance( from->ghoul2Model, &to->ghoul2Model ); + } + else + { + assert(0); + Com_Error(ERR_DROP, "CG_CopyClientInfoModel invalid g2 pointer\n"); + } + + memcpy( to->animations, from->animations, sizeof( to->animations ) ); + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) +{ + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + match = &cgs.clientinfo[ i ]; + + if ( !match->infoValid ) + { + continue; + } + if ( match->deferred ) + { + continue; + } + + // Identity name doesnt match + if ( Q_stricmp ( ci->identityName, match->identityName ) ) + { + continue; + } + + if ( ci->team == TEAM_FREE || + ci->team == TEAM_SPECTATOR || + ci->team == match->team ) + { + // this clientinfo is identical, so use it's handles + ci->deferred = qfalse; + + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) +{ + int i; + clientInfo_t *match; + +/* + // If someone else is already the same models and skins we + // can just load the client info + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + match = &cgs.clientinfo[ i ]; + + // Cant steal the model from invalid or deferred clients + if ( !match->infoValid || match->deferred ) + { + continue; + } + + // If the model, skin, and team match then we have a match + if ( Q_stricmp( ci->identityName, match->identityName) || + (ci->team != match->team) ) + { + continue; + } + + // just load the real info cause it uses the same models and skins + CG_LoadClientInfo( ci ); + + return; + } +*/ + + // Try to use any skin from the team they are joining + if ( cgs.gametypeData->teams ) + { + for ( i = 0; i < cgs.maxclients; i ++ ) + { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) + { + continue; + } + + // Must be on the same team + if ( match->team != ci->team ) + { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + } + // find the first valid clientinfo and grab its stuff + else + { + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) + { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + } + + CG_LoadClientInfo( ci ); +} + +/* +====================== +CG_ResetClientIdentity +====================== +*/ +void CG_ResetClientIdentity ( int clientNum ) +{ + clientInfo_t *ci; + centity_t *cent; + int i; + + ci = &cgs.clientinfo[clientNum]; + cent = CG_GetEntity ( clientNum ); + + // Clean up the raw ghoul model + if ( ci->ghoul2Model ) + { + trap_G2API_CleanGhoul2Models ( &ci->ghoul2Model ); + ci->ghoul2Model = NULL; + } + + // Clean up the active ghoul model + if ( cent->ghoul2 ) + { + trap_G2API_CleanGhoul2Models ( ¢->ghoul2 ); + cent->pe.weaponModelSpot = 0; + cent->pe.weapon = 0; + cent->ghoul2 = NULL; + } + + // Reset all bolt position + ci->boltWorldWeapon = -1; + ci->boltNightvision = -1; + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + ci->boltGametypeItems[i] = -1; + } +} + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) +{ + clientInfo_t *ci; + clientInfo_t newInfo; + centity_t* cent; + const char *configstring; + const char *v; + + ci = &cgs.clientinfo[clientNum]; + + cent = CG_GetEntity ( clientNum ); + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) + { + memset( ci, 0, sizeof( *ci ) ); + return; + } + + // Clear the client models / etc + CG_ResetClientIdentity ( clientNum ); + + ci->infoValid = qfalse; + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // team task + v = Info_ValueForKey( configstring, "tt" ); + newInfo.teamTask = atoi(v); + + // team leader + v = Info_ValueForKey( configstring, "tl" ); + newInfo.teamLeader = atoi(v); + + // identity + v = Info_ValueForKey( configstring, "identity" ); + Q_strncpyz ( newInfo.identityName, v, sizeof(newInfo.identityName) ); + + // Inform the ui of the team switch + if ( clientNum == cg.clientNum ) + { + char temp[MAX_QPATH]; + const char* identityCvar; + + trap_Cvar_Set ( "ui_info_team", va("%i",newInfo.team ) ); + + if (cgs.gametypeData->teams ) + { + identityCvar = "team_identity"; + } + else + { + identityCvar = "identity"; + } + + trap_Cvar_VariableStringBuffer ( identityCvar, temp, sizeof(temp) ); + + if ( Q_stricmp ( temp, newInfo.identityName ) ) + { + trap_Cvar_Set ( identityCvar, newInfo.identityName ); + } + } + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo ) ) + { + qboolean forceDefer; + + forceDefer = trap_MemoryRemaining() < 4000000; + + // if we are defering loads, just have it pick the first valid + if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) + { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + + // if we are low on memory, leave them with this model + if ( forceDefer ) + { + Com_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } + else + { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) +{ + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) + { + if ( ci->infoValid && ci->deferred ) + { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) + { + Com_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + + // Clear the client models / etc + CG_ResetClientIdentity ( i ); + + CG_LoadClientInfo( ci ); + } + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) +{ + int t; + float f; + + t = cg.time - cent->pe.painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) +{ + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + { + // only show in mirrors + rf = RF_THIRD_PERSON; + } + else + { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 58; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) +{ + int team; + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.number == cg.snap->ps.clientNum) && + !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametypeData->teams ) + { + if (cg_drawFriend.integer) + { + CG_PlayerFloatSprite( cent, team==TEAM_RED?cgs.media.redFriendShader:cgs.media.blueFriendShader ); + } + + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 256 +qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) +{ + vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; + trace_t trace; + float alpha; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { + return qfalse; + } + + *shadowPlane = trace.endpos[2] + 1; + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + alpha = 1.0; + trap_R_AddDecalToScene ( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->currentState.apos.trBase[YAW], 1.0,0.0,1.0,1.0, qfalse, 24, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + +/* +=============== +CG_UpdatePlayerAnimations +=============== +*/ +static void CG_UpdatePlayerAnimations ( centity_t* cent ) +{ + clientInfo_t *ci; + + // Make sure its a player + assert ( cent->currentState.eType == ET_PLAYER ); + + // Retrieve the client info pointer for the given client + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + + // Update the leg animation + if ( cent->pe.legs.anim != cent->currentState.legsAnim && cent->currentState.legsAnim != -1 ) + { + animation_t *anim; + int flags; + float speed; + + anim = &ci->animations[ (cent->currentState.legsAnim & (~ANIM_TOGGLEBIT)) ]; + + if ( anim->loopFrames == -1 ) + { + flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE; + } + else + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + + speed = 1.0f; + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / anim->frameLerp * speed, cg.time, -1, 150); + + cent->pe.legs.anim = cent->currentState.legsAnim; + cent->pe.legs.animTime = cg.time; + } + + // Update the torso animation + if ( cent->pe.torso.anim != cent->currentState.torsoAnim && cent->currentState.torsoAnim != -1 ) + { + animation_t *anim; + int flags; + float time; + anim = &ci->animations[ (cent->currentState.torsoAnim & (~ANIM_TOGGLEBIT)) ]; + + if ( anim->loopFrames == -1 ) + { + flags = BONE_ANIM_OVERRIDE|BONE_ANIM_OVERRIDE_FREEZE; + } + else + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + + if ( cent->currentState.torsoTimer > 0 ) + { + time = cent->currentState.torsoTimer; + time /= anim->numFrames; + } + else + { + time = anim->frameLerp; + } + + // Default to 20 FPS. + if ( time <= 0 ) + { + time = 1000.0f / 20.0f; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, flags, 50.0f / time, cg.time, -1, 150); + + cent->pe.torso.anim = cent->currentState.torsoAnim ; + cent->pe.torso.animTime = cg.time; + } +} + +/* +=============== +CG_PlayerGametypeItems +=============== +*/ +void CG_PlayerGametypeItems ( centity_t* cent, vec3_t angles ) +{ + clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum]; + int i; + int rf; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) + { + // only show in mirrors + rf = RF_THIRD_PERSON; + } + else + { + rf = 0; + } + + // If the player is wearing goggles then draw them on their head + if ( cent->currentState.eFlags & EF_GOGGLES ) + { + refEntity_t item; + + if ( ci->boltNightvision == -1 ) + { + ci->boltNightvision = trap_G2API_AddBolt( cent->ghoul2, 0, "*head_t" ); + } + + memset ( &item, 0, sizeof(item) ); + item.renderfx = rf | RF_MINLIGHT; + item.ghoul2 = cgs.media.nightVisionModel; + G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltNightvision, cent->lerpOrigin, angles, cent->modelScale ); + trap_R_AddRefEntityToScene(&item); + } + + + // Loop through the gametye items and handle each separately + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + // If they dont have the item then skip it + if ( !(cent->currentState.gametypeitems & (1<boltGametypeItems[i] == -1 ) + { + ci->boltGametypeItems[i] = trap_G2API_AddBolt( cent->ghoul2, 0, "*back" ); + if ( ci->boltGametypeItems[i] == -1 ) + { + continue; + } + + // Remove all identity items on the back + CG_RemoveIdentityItemsOnBack ( cent ); + } + + memset ( &item, 0, sizeof(item) ); + item.renderfx = rf | RF_MINLIGHT; + item.ghoul2 = cg_items[ MODELINDEX_GAMETYPE_ITEM + i ].boltModel; + G2_PositionEntityOnBolt ( &item, cent->ghoul2, 0, ci->boltGametypeItems[i], cent->lerpOrigin, angles, cent->modelScale ); + trap_R_AddRefEntityToScene(&item); + } + } +} + +/* +=============== +CG_UpdatePlayerWeaponModel +=============== +*/ +void CG_UpdatePlayerWeaponModel ( centity_t* cent, qboolean force ) +{ + clientInfo_t *ci = &cgs.clientinfo[cent->currentState.clientNum]; + + // If the weapon model hasnt changed then dont update it + if ( !force && cent->currentState.weapon == cent->pe.weapon ) + { + return; + } + + // Ensure the weapon is registered + CG_RegisterWeapon ( cent->currentState.weapon ); + + // If we have a ghoul model then we can attach it to the right hand of the player model + if (cg_weapons[cent->currentState.weapon].weaponG2Model) + { + if ( cent->pe.weaponModelSpot ) + { + trap_G2API_DetachG2Model ( cent->ghoul2, cent->pe.weaponModelSpot ); + trap_G2API_RemoveGhoul2Model( ¢->ghoul2, cent->pe.weaponModelSpot ); + cent->pe.weaponModelSpot = 0; + } + + cent->pe.weaponModelSpot = trap_G2API_CopySpecificGhoul2Model(cg_weapons[cent->currentState.weapon].weaponG2Model, 0, cent->ghoul2, -1 ); +// trap_G2API_CopySpecificGhoul2Model(cg_weapons[cent->currentState.weapon].weaponG2Model, 0, cent->ghoul2, cent->pe.weaponModelSpot ); + trap_G2API_SetBoltInfo(cent->ghoul2, cent->pe.weaponModelSpot, ci->boltWorldWeapon ); + trap_G2API_AttachG2Model(cent->ghoul2, cent->pe.weaponModelSpot, cent->ghoul2, ci->boltWorldWeapon, 0); + + // Special case to make sure the bayonet shows up for the ak74 + switch ( cent->currentState.weapon ) + { + case WP_AK74_ASSAULT_RIFLE: + trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "bayonet_off", 0 ); + break; + + case WP_M4_ASSAULT_RIFLE: + trap_G2API_SetSurfaceOnOff( cent->ghoul2, cent->pe.weaponModelSpot, "m203_off", 0 ); + break; + } + } + + // Update the weapon in the player entity + cent->pe.weapon = cent->currentState.weapon; +} + +/* +=============== +CG_UpdatePlayerModel +=============== +*/ +void CG_UpdatePlayerModel ( centity_t* cent ) +{ + if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model)) + { + CG_ResetPlayerEntity ( cent ); + + if (!cgs.clientinfo[cent->currentState.clientNum].ghoul2Model) + { + Com_Error(ERR_DROP, "CG_UpdatePlayerModel invalid g2 pointer for client\n"); + } + trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[cent->currentState.clientNum].ghoul2Model, ¢->ghoul2); + + // Force the players weapon model to be updated since their main model changed + CG_UpdatePlayerWeaponModel ( cent, qtrue ); + } +} + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) +{ + clientInfo_t *ci; + refEntity_t player; + int clientNum; + int renderfx; + qboolean shadow; + float shadowPlane; + int iwantout = 0; + vec3_t save; + + // Dont draw any player models when the round has ended + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + return; + } + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) + { + Com_Error( ERR_FATAL, "Bad clientNum on player entity"); + } + + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) + { + return; + } + + // don't draw the death sequences, as the body queue should be doing it for us + if ( cent->currentState.eFlags & EF_DEAD ) + { + return; + } + + // Add the player to the radar if on the same team and its a team game + if ( cgs.gametypeData->teams ) + { + if ( cg.snap->ps.clientNum != cent->currentState.number && ci->team == cgs.clientinfo[ cg.snap->ps.clientNum ].team ) + { + cg.radarEntities[cg.radarEntityCount++] = cent; + } + } + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum) + { + // If rendering third person then we shouldnt draw the player if + // the view origin is too close to the player (ie, inside it) + if ( cg.renderingThirdPerson ) + { + vec3_t diff; + vec3_t a; + vec3_t b; + float f; + + VectorCopy ( cg.refdef.vieworg, a ); + VectorCopy ( cent->lerpOrigin, b ); +// a[2] = 0; +// b[2] = 0; + + VectorSubtract ( a, b, diff ); + + f = VectorLengthSquared ( diff ); + if ( VectorLengthSquared ( diff ) < 1800 ) + { + renderfx = RF_THIRD_PERSON; + } + } + // only draw in mirrors + else + { + renderfx = RF_THIRD_PERSON; + } + } + + // Initialize the ref entity + memset (&player, 0, sizeof(player)); + + // NOTENOTE Temporary + VectorSet(player.modelScale, 1,1,1); + player.radius = 64; + VectorClear(player.angles); + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + if (iwantout) + { + return; + } + + // add a water splash if partially in and out of water +// CG_PlayerSplash( cent ); + + if ( cg_shadows.integer == 3 && shadow ) + { + renderfx |= RF_SHADOW_PLANE; + } + + // use the same origin for all + renderfx |= RF_LIGHTING_ORIGIN | RF_MINLIGHT; + + VectorCopy( cent->lerpOrigin, player.origin ); + VectorCopy( cent->lerpOrigin, player.lightingOrigin ); + + player.shadowPlane = shadowPlane; + player.renderfx = renderfx; + + // don't positionally lerp at all + VectorCopy (player.origin, player.oldorigin); + + // get the animation state (after rotation, to allow feet shuffle) + CG_UpdatePlayerModel ( cent ); + CG_UpdatePlayerAnimations ( cent ); + CG_UpdatePlayerWeaponModel ( cent, qfalse ); + + if ( (cent->currentState.eFlags & EF_DEAD ) ) + { + AnglesToAxis( cent->lerpAngles, player.axis ); + } + else + { + // Force the legs and torso to stay aligned for now to ensure the client + // and server are in sync with the angles. + // TODO: Come up with a way to keep these in sync on both client and server + cent->pe.torso.yawing = qtrue; + cent->pe.torso.pitching = qtrue; + cent->pe.legs.yawing= qtrue; + + BG_PlayerAngles( cent->lerpAngles, + player.axis, + + cent->pe.ghoulLegsAngles, + cent->pe.ghoulLowerTorsoAngles, + cent->pe.ghoulUpperTorsoAngles, + cent->pe.ghoulHeadAngles, + + cent->lerpLeanOffset, + + cent->pe.painTime, + cent->pe.painDirection, + cg.time, + + ¢->pe.torso, + ¢->pe.legs, + + cg.frametime, + cent->lerpVelocity, + (cent->currentState.eFlags & EF_DEAD), + cent->currentState.angles2[YAW], + cent->ghoul2 ); + } + + VectorCopy ( cent->lerpOrigin, save ); + VectorCopy ( player.origin, cent->lerpOrigin ); + + CG_ScaleModelAxis(&player); + + // Copy the ghoul 2 info into the ref entity + CG_SetGhoul2Info(&player, cent); + + // Now add the player to the scene + trap_R_AddRefEntityToScene(&player); + + // + // add the gun / barrel / flash + // + // need the angle AFTER the lean is added +// vectoangles( player.axis[0], cent->pe.ghoulRootAngles ); + + // Render any weapon related effects + CG_PlayerWeaponEffects ( &player, cent, ci->team, cent->pe.ghoulLegsAngles ); + + // Render any of the floating sprites above the players head + CG_PlayerSprites ( cent ); + + CG_PlayerGametypeItems ( cent, cent->pe.ghoulLegsAngles ); + + VectorCopy ( save, cent->lerpOrigin ); +} + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) +{ + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + cent->pe.legs.anim = -1; + cent->pe.torso.anim = -1; + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; +} + +/* +================= +CG_ProcessIdentityItems + +Attaches all the items for a character skin +================= +*/ +static void CG_ProcessIdentityItems ( TGhoul2 ghoul2, TInventoryTemplate *items ) +{ + TSurfaceList *surf; + + while ( items ) + { + if ( items->mItem ) + { + if ( items->mItem->mModel && items->mBolt ) + { + items->mModelIndex = trap_G2API_InitGhoul2Model(&ghoul2, items->mItem->mModel, 0, 0, 0, 0, 0); + if (items->mModelIndex != -1) + { + items->mBoltIndex = trap_G2API_AddBolt ( ghoul2, 0, items->mBolt); + if (items->mBoltIndex != -1) + { + trap_G2API_AttachG2Model( ghoul2, items->mModelIndex, ghoul2, items->mBoltIndex, 0); + } + } + } + + surf = items->mItem->mOffList; + while(surf) + { + trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF); + + surf = surf->mNext; + } + + surf = items->mItem->mOnList; + while(surf) + { + trap_G2API_SetSurfaceOnOff( ghoul2, 0, surf->mName, 0); + + surf = surf->mNext; + } + } + + items = items->mNext; + } +} + +/* +================= +CG_RemoveIdentityItemsOnBack + +Removes all identity items on the back of the given entity. There is +currently no way to get the identity items back after removing them +without killing off the player. +================= +*/ +void CG_RemoveIdentityItemsOnBack ( centity_t* cent ) +{ + TInventoryTemplate* items[2]; + TIdentity* identity; + int pass; + + // Grab the identity structure for this entity + identity = cgs.clientinfo[cent->currentState.number].identity; + if ( !identity ) + { + return; + } + + // Takes two passes to remove em all since the item lists are in two places + items[0] = identity->mCharacter->mInventory; + items[1] = identity->mSkin->mInventory; + + for ( pass = 0; pass < 2; pass ++ ) + { + while ( items[pass] ) + { + if ( items[pass]->mOnBack && items[pass]->mItem ) + { + TSurfaceList *surf; + + // Bolt on? + if ( items[pass]->mBolt && *items[pass]->mBolt) + { + trap_G2API_DetachG2Model ( cent->ghoul2, items[pass]->mModelIndex ); + trap_G2API_RemoveGhoul2Model ( ¢->ghoul2, items[pass]->mModelIndex ); + } + + // Surface list? Reversed from what process does + surf = items[pass]->mItem->mOffList; + while(surf) + { + trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, 0); + + surf = surf->mNext; + } + + surf = items[pass]->mItem->mOnList; + while(surf) + { + trap_G2API_SetSurfaceOnOff( cent->ghoul2, 0, surf->mName, G2SURFACEFLAG_OFF); + + surf = surf->mNext; + } + } + + items[pass] = items[pass]->mNext; + } + } +} + +/* +================= +CG_RegisterIdentity + +Registers an identity +================= +*/ +TGhoul2 CG_RegisterIdentity ( TIdentity* identity, char *animationFile, gender_t* gender ) +{ + char name[MAX_QPATH]; + TGenericParser2 skinFile; + TGPGroup *basegroup, *group, *sub; + char temp[20480], *end; + int numPairs; + TGhoul2 ghoul2Ptr; + + numPairs = 0; + end = temp; + *end = 0; + ghoul2Ptr = NULL; + + *animationFile = 0; + + if (!identity ) + { + return NULL; + } + + if (trap_G2API_InitGhoul2Model( &ghoul2Ptr, identity->mCharacter->mModel, 0, 0, 0, (1<<4), 0) == -1) + { + return NULL; + } + + trap_G2API_GetAnimFileNameIndex( ghoul2Ptr, 0, name ); + Com_sprintf(animationFile, MAX_QPATH, "%s_mp.cfg", name ); + + if ( identity->mCharacter->mParent) + { + CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mParent->mInventory ); + } + + CG_ProcessIdentityItems( ghoul2Ptr, identity->mCharacter->mInventory); + CG_ProcessIdentityItems( ghoul2Ptr, identity->mSkin->mInventory); + + // don't need the mouth + trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_r", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS); + trap_G2API_SetSurfaceOnOff( ghoul2Ptr, 0, "mouth_l", G2SURFACEFLAG_OFF|G2SURFACEFLAG_NODESCENDANTS); + + // Parse the g2skin file + Com_sprintf( name, sizeof(name), "models/characters/skins/%s.g2skin", identity->mSkin->mSkin ); + skinFile = trap_GP_ParseFile( name, qtrue, qfalse ); + if ( !skinFile ) + { + trap_G2API_CleanGhoul2Models( &ghoul2Ptr); + return NULL; + } + + basegroup = trap_GP_GetBaseParseGroup ( skinFile ); + group = trap_GPG_GetSubGroups ( basegroup ); + + while(group) + { + trap_GPG_GetName ( group, name ); + + // Parse the material + if ( Q_stricmp ( name, "material") == 0) + { + char matName[MAX_QPATH]; + char shaderName[MAX_QPATH]; + + trap_GPG_FindPairValue ( group, "name", "", matName ); + + sub = trap_GPG_FindSubGroup ( group, "group"); + if (sub) + { + trap_GPG_FindPairValue ( sub, "shader1", "", shaderName ); + if (!shaderName[0]) + { + trap_GPG_FindPairValue ( sub, "texture1", "", shaderName ); + } + } + + if (matName[0] && shaderName[0]) + { + end += Com_sprintf(end, sizeof(temp) - (end-temp+1), "%s %s ", matName, shaderName); + numPairs++; + } + } + + group = trap_GPG_GetNext ( group ); + } + + trap_GP_Delete(&skinFile); + + if (numPairs) + { + qhandle_t handle; + + handle = trap_G2API_RegisterSkin( identity->mName, numPairs, temp); + trap_G2API_SetSkin( ghoul2Ptr, 0, handle); + } + + // If the gender was requested then look for female in the model + // name as the indicator + if ( gender ) + { + Q_strlwr ( (char*)identity->mCharacter->mModel ); + + *gender = GENDER_MALE; + if ( strstr ( identity->mCharacter->mModel, "female" ) ) + { + *gender = GENDER_FEMALE; + } + } + + return ghoul2Ptr; +} diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c new file mode 100644 index 0000000..5be301e --- /dev/null +++ b/code/cgame/cg_playerstate.c @@ -0,0 +1,313 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) +{ + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) + { + scale = 1; + } + else + { + scale = 40.0 / health; + } + + kick = damage * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) + { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 255; + cg.v_dmg_pitch = -kick; + } + else + { + float front; + float left; + + angles[PITCH] = (float)pitchByte / 255.0f * 360.0f; + angles[YAW] = (float)yawByte / 255.0f * 360.0f; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg.refdef.viewaxis[0] ); + left = DotProduct (dir, cg.refdef.viewaxis[1] ); + + cg.v_dmg_roll = kick * left; + cg.v_dmg_pitch = -kick * front; + + cg.damageY = -AngleNormalize180(angles[PITCH]); + cg.damageX = AngleNormalize180(angles[YAW] - cg.predictedPlayerState.viewangles[YAW] + 180); + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) + { + kick = 10; + } + + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) +{ + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; + + // no more camera shake + cg.shakeStart = 0; + + // Make sure the weapon selection menu isnt up + cg.weaponMenuUp = qfalse; + + // clear any left over flash grenades + cg.flashbangTime = 0; + + // Reset the animation + CG_SetWeaponAnim( cg.snap->ps.weaponAnimId&(~ANIM_TOGGLEBIT), &cg.snap->ps ); + + // Update the view weapon surfaces + CG_UpdateViewWeaponSurfaces ( &cg.snap->ps ); + + trap_ResetAutorun ( ); +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) +{ + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) + { + cent = CG_GetEntity ( ps->clientNum ); + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg_entities[ ps->clientNum ]; + + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) +{ + int i; + int event; + centity_t *cent; + + cent = &cg_entities[ps->clientNum]; + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) + { + // + if (i >= cg.eventSequence) + { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) + { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) + { + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + if ( cg_showmiss.integer ) + { + Com_Printf("WARNING: changed predicted event\n"); + } + } + } + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) +{ + // don't play the sounds if the player just changed teams + if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) + { + return; + } + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) + { + if ( ps->stats[STAT_HEALTH] > 0 ) + { + CG_PainEvent( &cg_entities[ps->clientNum], ps->stats[STAT_HEALTH] ); + } + } + + // Look for a zoom transition that isnt the first zoom in + if ( ops->zoomFov && (ps->zoomFov != ops->zoomFov) ) + { + trap_S_StartLocalSound ( cgs.media.zoomSound, CHAN_AUTO ); + } +} + +/* +=============== +CG_TransitionPlayerState +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) +{ + // respawning. This is done before the follow mode check because spawn count is + // maintained into the following client + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) + { + CG_Respawn(); + } + + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) + { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) + { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // Make sure we clear the weapon menu when we die + if ( ps->stats[STAT_HEALTH] != ops->stats[STAT_HEALTH] ) + { + if ( ps->stats[STAT_HEALTH] <= 0 ) + { + cg.weaponMenuUp = qfalse; + cg.deathTime = cg.time; + trap_ResetAutorun ( ); + } + } + + if ( cg.mapRestart ) + { + CG_Respawn(); + cg.mapRestart = qfalse; + } + + if ( cg.snap->ps.pm_type != PM_INTERMISSION && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + CG_CheckLocalSounds( ps, ops ); + } + + // Always use the weapon from the player state when following + if( (ps->pm_flags & PMF_FOLLOW) || (ps->weapon != ops->weapon) ) + { + cg.weaponSelect = ps->weapon; + } + + // Check for weapon animation change. + if(ps->weaponAnimId!=ops->weaponAnimId ) + { + CG_SetWeaponAnim(ps->weaponAnimId&(~ANIM_TOGGLEBIT),ps); + } + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change and not crouch jumping + if ( ps->viewheight != ops->viewheight && !(ps->pm_flags & PMF_CROUCH_JUMP) ) + { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } + + // Need to update the view weapon surfaces when weapon states change + if ( ps->weaponstate != ops->weaponstate ) + { + CG_UpdateViewWeaponSurfaces ( ps ); + } +} + diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c new file mode 100644 index 0000000..eb5e27c --- /dev/null +++ b/code/cgame/cg_predict.c @@ -0,0 +1,828 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_GENTITIES]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) +{ + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + vec3_t difference; + float dsquared; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) + { + snap = cg.nextSnap; + } + else + { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) + { + cent = CG_GetEntity ( snap->entities[ i ].number ); + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) + { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) + { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } + + dsquared = RMG_distancecull.value+500; + dsquared *= dsquared; + + for(i=0;ilerpOrigin, snap->ps.origin, difference); + if (cent->currentState.eType == ET_TERRAIN || + ((difference[0]*difference[0]) + (difference[1]*difference[1]) + (difference[2]*difference[2])) <= dsquared) + { + cent->currentValid = qtrue; + if ( cent->nextState.solid ) + { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + } + } + else + { + cent->currentValid = qfalse; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, trace_t *tr ) { + int i, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } else { + // encoded bbox + x = (ent->solid & 255); + zd = ((ent->solid>>8) & 255); + zu = ((ent->solid>>16) & 255) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + + + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles); + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + } + if ( tr->allsolid ) { + return; + } + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask ) +{ + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); + + *result = t; +} + +/* +================ +CG_PlayerTrace + +Trace against all players in the game and return a precise ghoul location where they were hit +================ +*/ +void CG_PlayerTrace ( trace_t* tr, const vec3_t start, const vec3_t end, int skipNumber ) +{ + G2Trace_t G2Trace; + int i; + float hitlen; + int hit; + vec3_t hitloc; + + hit = -1; + + VectorSubtract ( end, start, hitloc ); + hitlen = VectorLengthSquared ( hitloc ); + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + centity_t *cent; + animation_t *anim; + + cent = &cg_entities[i]; + + // Look for reasons to not even bother with this client + if ( !cent->currentValid || cent->currentState.number == skipNumber || cent->currentState.eType != ET_PLAYER ) + { + continue; + } + + // Cant hit dead people + if ( cent->currentState.eFlags & EF_DEAD ) + { + continue; + } + + // Do a quick test to make sure its even close to the guy + if ( !Q_TestRaySphere ( cent->lerpOrigin, 50, start, end ) ) + { + continue; + } + + // See if the shot is even close to the player + G2Trace[0].mEntityNum = -1; + + anim = &cg.hitAnimations[ cent->pe.legs.anim &(~ANIM_TOGGLEBIT) ]; + trap_G2API_SetBoneAnim(cg.hitModel, 0, "model_root", anim->firstFrame, anim->firstFrame + anim->numFrames, BONE_ANIM_OVERRIDE_LOOP, 50.0f / anim->frameLerp, cent->pe.legs.animTime, -1, 0); + + anim = &cg.hitAnimations[ cent->pe.torso.anim &(~ANIM_TOGGLEBIT)]; + trap_G2API_SetBoneAnim(cg.hitModel, 0, "lower_lumbar", anim->firstFrame, anim->firstFrame + anim->numFrames, BONE_ANIM_OVERRIDE_LOOP, 50.0f / anim->frameLerp, cent->pe.torso.animTime, -1, 0); + + trap_G2API_SetBoneAngles( cg.hitModel, 0, "upper_lumbar", cent->pe.ghoulUpperTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, cg.time ); + trap_G2API_SetBoneAngles( cg.hitModel, 0, "lower_lumbar", cent->pe.ghoulLowerTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, cg.time ); + trap_G2API_SetBoneAngles( cg.hitModel, 0, "cranium", cent->pe.ghoulHeadAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, 0,0, cg.time ); + + trap_G2API_CollisionDetect ( G2Trace, cg.hitModel, cent->pe.ghoulLegsAngles, cent->lerpOrigin, cg.time, cent->currentState.number, start, end, vec3_identity, 0, 2 ); + + if ( G2Trace[0].mEntityNum == i ) + { + vec3_t temp; + float len; + + VectorSubtract ( G2Trace[0].mCollisionPosition, start, temp ); + len = VectorLengthSquared ( temp ); + + if ( len < hitlen ) + { + hitlen = len; + hit = i; + VectorCopy ( G2Trace[0].mCollisionPosition, hitloc ); + } + } + } + + if ( hit != -1 ) + { + tr->entityNum = hit; + VectorCopy ( hitloc, tr->endpos ); + } +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if (ent->solid != SOLID_BMODEL) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) + { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg.nextFrameTeleport ) { + return; + } + + if ( !next ) + { + return; + } + +#ifndef _SNAPSHOT_EXTRAPOLATION + + if ( next->serverTime <= prev->serverTime ) + { + return; + } + +#endif + + f = (float)( cg.time - prev->serverTime ) / (float)( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) +{ + gitem_t *item; + qboolean autoswitch = qfalse; + + if ( !cg_predictItems.integer ) + { + return; + } + + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) + { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) + { + return; + } + + if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) + { + // can't hold it + return; + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + // No item prediction on gametype items + if ( item->giType == IT_GAMETYPE || item->giType == IT_BACKPACK ) + { + return; + } + + // See if we should autoswitch + if ( bg_itemlist[cent->currentState.modelindex].giType == IT_WEAPON ) + { + // Dont autoswitch if the weapon isnt a safe weapon + if ( cg_autoswitch.integer == 1 || weaponData[bg_itemlist[cent->currentState.modelindex].giTag].safe ) + { + if ( !(cg.predictedPlayerState.stats[STAT_WEAPONS] & (1<currentState.modelindex].giTag)) ) + { + autoswitch = qtrue; + } + } + } + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex|(autoswitch?ITEM_AUTOSWITCHBIT:0) , &cg.predictedPlayerState); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg.time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) + { + cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; + if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) + { + cg.predictedPlayerState.ammo[ item->giTag ] = 1; + } + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + + if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } + + if ( ent->eType == ET_TELEPORT_TRIGGER ) + { + cg.hyperspace = qtrue; + } + } +} + + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) +{ + int cmdNum; + int current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + + // will be set if touching a trigger_teleport + cg.hyperspace = qfalse; + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) + { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) + { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) + { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove +#if _Debug + cg_pmove.isClient=-1; +#endif + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + if ( cg_pmove.ps->pm_type == PM_DEAD ) + { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else + { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime && oldestCmd.serverTime < cg.time ) + { + // special check for map_restart + if ( cg_showmiss.integer ) + { + Com_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) + { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } + else + { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) + { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) + { + trap_Cvar_Set("pmove_msec", "33"); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) + { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if ( cg_pmove.pmove_fixed ) + { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) + { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) + { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) + { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) + { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) + { + Com_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } + else + { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showmiss.integer ) + { + if (!VectorCompare( oldPlayerState.origin, adjusted )) + { + Com_Printf("prediction error\n"); + } + } + + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) + { + if ( cg_showmiss.integer ) + { + Com_Printf("Prediction miss: %f\n", len); + } + + if ( cg_errorDecay.integer ) + { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) + { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) + { + Com_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } + else + { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + cg_pmove.gauntletHit = qfalse; + + if ( cg_pmove.pmove_fixed ) + { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + cg_pmove.animations = cgs.clientinfo[cg.clientNum].animations; + + Pmove (&cg_pmove); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); + } + + if ( cg_showmiss.integer > 1 ) + { + Com_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) + { + if ( cg_showmiss.integer ) + { + Com_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + if ( cg_showmiss.integer ) + { + if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) + { + Com_Printf("WARNING: dropped event\n"); + } + } + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + if ( cg_showmiss.integer ) + { + if (cg.eventSequence > cg.predictedPlayerState.eventSequence) + { + Com_Printf("WARNING: double event\n"); + cg.eventSequence = cg.predictedPlayerState.eventSequence; + } + } +} + +/* +=============== +CG_InitHitModel +=============== +*/ +void CG_InitHitModel ( void ) +{ + void* ghoul2; + char temp[20480]; + int numPairs; + qhandle_t handle; + + ghoul2 = NULL; + + // Initialize the ghoul2 model + trap_G2API_InitGhoul2Model ( &ghoul2, + "models/characters/average_sleeves/average_sleeves.glm", + 0, 0, 0, (1<<4), 2 ); + + // Verify it + if ( !ghoul2 ) + { + return; + } + + // Parse the skin file that will be used for hit information + numPairs = BG_ParseSkin ( "models/characters/skins/col_rebel_h1.g2skin", temp, sizeof(temp) ); + if ( !numPairs ) + { + trap_G2API_CleanGhoul2Models ( &ghoul2 ); + return; + } + + // Register the skin and attach it to the ghoul model + handle = trap_G2API_RegisterSkin( "hitmodel", numPairs, temp ); + trap_G2API_SetSkin( ghoul2, 0, handle ); + + // Read in the animations for this model + trap_G2API_GetAnimFileNameIndex( ghoul2, 0, temp ); + BG_ParseAnimationFile ( va("%s_mp.cfg", temp), cg.hitAnimations ); + + // Hand the hit model back + cg.hitModel = ghoul2; +} diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h new file mode 100644 index 0000000..d09bcaa --- /dev/null +++ b/code/cgame/cg_public.h @@ -0,0 +1,439 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_public.h -- + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum +{ + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_FS_GETFILELIST, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_RMG_INIT, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STOPALLSOUNDS, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_AS_ADDPRECACHEENTRY, + CG_AS_PARSESETS, + CG_AS_UPDATEAMBIENTSET, + CG_AS_ADDLOCALSET, + CG_AS_GETBMODELSOUND, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_CLEARDECALS, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDDECALTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_DRAWVISUALOVERLAY, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_R_DRAWROTATEPIC, + CG_R_DRAWROTATEPIC2, + CG_R_DRAWTEXT, + CG_R_DRAWTEXTWITHCURSOR, + CG_R_GETTEXTWIDTH, + CG_R_GETTEXTHEIGHT, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETDEFAULTSTATE, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_RW_SETTEAM, + CG_RESETAUTORUN, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_PC_LOAD_GLOBAL_DEFINES, + CG_PC_REMOVE_ALL_GLOBAL_DEFINES, + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_ANGLEVECTORS, + CG_PERPENDICULARVECTOR, + CG_FLOOR, + CG_CEIL, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS, + CG_ASIN, + CG_MATRIXMULTIPLY, + + + CG_SP_GETSTRINGTEXTSTRING, + + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_R_GET_LIGHT_STYLE, + CG_R_SET_LIGHT_STYLE, + CG_FX_ADDLINE, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + + CG_FX_REGISTER_EFFECT, + CG_FX_PLAY_SIMPLE_EFFECT, + CG_FX_PLAY_EFFECT, + CG_FX_PLAY_ENTITY_EFFECT, + CG_FX_PLAY_SIMPLE_EFFECT_ID, + CG_FX_PLAY_EFFECT_ID, + CG_FX_PLAY_ENTITY_EFFECT_ID, + CG_FX_PLAY_BOLTED_EFFECT_ID, + CG_FX_ADD_SCHEDULED_EFFECTS, + CG_FX_INIT_SYSTEM, + CG_FX_FREE_SYSTEM, + CG_FX_ADJUST_TIME, + CG_FX_DRAW_2D_EFFECTS, + CG_FX_RESET, + + CG_G2_LISTBONES, + CG_G2_LISTSURFACES, + + CG_G2_ADDBOLT, + CG_G2_SETBOLTON, + CG_G2_REMOVEBOLT, + CG_G2_ATTACHG2MODEL, + CG_G2_DETACHG2MODEL, + + CG_G2_HAVEWEGHOULMODELS, + CG_G2_SETMODELS, + CG_G2_GETBOLT, + CG_G2_INITGHOUL2MODEL, + CG_G2_CLEANMODELS, + CG_G2_ANGLEOVERRIDE, + CG_G2_PLAYANIM, + CG_G2_GETANIM, + CG_G2_SETSURFACEONOFF, + CG_G2_SETROOTSURFACE, + CG_G2_SETNEWORIGIN, + CG_G2_GETGLANAME, + CG_G2_COPYGHOUL2INSTANCE, + CG_G2_COPYSPECIFICGHOUL2MODEL, + CG_G2_DUPLICATEGHOUL2INSTANCE, + CG_G2_REMOVEGHOUL2MODEL, + CG_G2_ADDSKINGORE, + CG_G2_CLEARSKINGORE, + CG_G2_SETGHOUL2MODELFLAGS, + CG_G2_GETGHOUL2MODELFLAGS, + CG_G2_SETGHOUL2MODELFLAGSBYINDEX, + CG_G2_GETGHOUL2MODELFLAGSBYINDEX, + CG_G2_GETNUMMODELS, + CG_G2_GETANIMFILENAMEINDEX, + CG_G2_FINDBOLTINDEX, + CG_G2_GETBOLTINDEX, + + CG_G2_REGISTERSKIN, + CG_G2_SETSKIN, + CG_G2_COLLISIONDETECT, + + CG_MAT_RESET, + CG_MAT_CACHE, + CG_MAT_GET_SOUND, + CG_MAT_GET_DECAL, + CG_MAT_GET_DECAL_SCALE, + CG_MAT_GET_EFFECT, + CG_MAT_GET_DEBRIS, + CG_MAT_GET_DEBRIS_SCALE, + + CG_CM_TM_CREATE, + CG_CM_TM_ADDBUILDING, + CG_CM_TM_ADDSPOT, + CG_CM_TM_ADDTARGET, + CG_CM_TM_UPLOAD, + CG_CM_TM_CONVERT_POS, + + // CGenericParser2 (void *) routines + GP_PARSE, + GP_PARSE_FILE, + GP_CLEAN, + GP_DELETE, + GP_GET_BASE_PARSE_GROUP, + + // CGPGroup (void *) routines + GPG_GET_NAME, + GPG_GET_NEXT, + GPG_GET_INORDER_NEXT, + GPG_GET_INORDER_PREVIOUS, + GPG_GET_PAIRS, + GPG_GET_INORDER_PAIRS, + GPG_GET_SUBGROUPS, + GPG_GET_INORDER_SUBGROUPS, + GPG_FIND_SUBGROUP, + GPG_FIND_PAIR, + GPG_FIND_PAIRVALUE, + + // CGPValue (void *) routines + GPV_GET_NAME, + GPV_GET_NEXT, + GPV_GET_INORDER_NEXT, + GPV_GET_INORDER_PREVIOUS, + GPV_IS_LIST, + GPV_GET_TOP_VALUE, + GPV_GET_LIST, + + CG_CM_REGISTER_TERRAIN, + CG_RE_INIT_RENDERER_TERRAIN, + + CG_SET_SHARED_BUFFER, + + CG_VM_LOCALALLOC, + CG_VM_LOCALALLOCUNALIGNED, + CG_VM_LOCALTEMPALLOC, + CG_VM_LOCALTEMPFREE, + CG_VM_LOCALSTRINGALLOC, + + CG_UI_SETACTIVEMENU, + CG_UI_CLOSEALL, + +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum +{ + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, map restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING, +// void (*CG_EventHandling)(int type); + + CG_POINT_CONTENTS, + + CG_GET_LERP_ORIGIN, +// void CG_LerpOrigin(int num, vec3_t result); + + CG_GET_LERP_ANGLES, + CG_GET_MODEL_SCALE, + CG_GET_GHOUL2, + CG_GET_MODEL_LIST, + + CG_CALC_LERP_POSITIONS, +// void CG_CalcEntityLerpPositions(int num); + + CG_TRACE, + + CG_GET_ORIGIN, // int entnum, vec3_t origin + CG_GET_ANGLES, // int entnum, vec3_t angle + + CG_GET_ORIGIN_TRAJECTORY, // int entnum + CG_GET_ANGLE_TRAJECTORY, // int entnum + + CG_FX_CAMERASHAKE, + + CG_MISC_ENT, + + CG_MAP_CHANGE, + +} cgameExport_t; + +// CG_POINT_CONTENTS +typedef struct +{ + vec3_t mPoint; // input + int mPassEntityNum; // input +} TCGPointContents; + +// CG_GET_LERP_ORIGIN +// CG_GET_LERP_ANGLES +// CG_GET_MODEL_SCALE +typedef struct +{ + int mEntityNum; // input + vec3_t mPoint; // output +} TCGVectorData; + +// CG_TRACE +typedef struct +{ + trace_t mResult; // output + vec3_t mStart, mMins, mMaxs, mEnd; // input + int mSkipNumber, mMask; // input +} TCGTrace; + +// CG_FX_CAMERASHAKE +typedef struct +{ + vec3_t mOrigin; // input + float mIntensity; // input + int mRadius; // input + int mTime; // input +} TCGCameraShake; + +// CG_MISC_ENT +typedef struct +{ + char mModel[MAX_QPATH]; // input + vec3_t mOrigin, mAngles, mScale; // input +} TCGMiscEnt; + +/// CG_CM_TM_CONVERT_POS +typedef struct +{ + vec3_t mOrigin; // input + int mWidth, mHeight; // input + int mX, mY; // output +} TCGConvertPos; + + +#define MAX_CG_SHARED_BUFFER_SIZE 2048 + + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + +//---------------------------------------------- diff --git a/code/cgame/cg_scoreboard.c b/code/cgame/cg_scoreboard.c new file mode 100644 index 0000000..733606b --- /dev/null +++ b/code/cgame/cg_scoreboard.c @@ -0,0 +1,521 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" + + +#define SB_HEADER 86 +#define SB_TOP (SB_HEADER+32) + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +#define SB_MAXCLIENTS_NORMAL 12 +#define SB_MAXCLIENTS_MORE 26 +#define SB_MAXCLIENTS_ALOT 32 + +// Used when interleaved +#define SB_HEADER_WIDTH (580) +#define SB_HEADER_HEIGHT 30 +#define SB_HEADER_X ((640-SB_HEADER_WIDTH)/2) +#define SB_HEADER_Y 86 +#define SB_SCORELINE_X (SB_HEADER_X+30) +#define SB_SCORELINE_Y 120 +#define SB_SCORELINE_WIDTH (SB_HEADER_WIDTH-60) +#define SB_NAME_X (SB_SCORELINE_X) +#define SB_SCORE_X (SB_SCORELINE_X+287) +#define SB_KILLS_X (SB_SCORELINE_X+335) +#define SB_DEATHS_X (SB_SCORELINE_X+384) +#define SB_PING_X (SB_SCORELINE_X+440) +#define SB_TIME_X (SB_SCORELINE_X+489) + +int sb_lineHeight; +int sb_maxClients; +float sb_nameFontScale; +float sb_numberFontScale; +float sb_readyFontScale; + +/* +================= +CG_DrawClientScore +================= +*/ +static void CG_DrawClientScore( float x, float y, float w, score_t* score, float* color ) +{ + clientInfo_t* ci; + vec4_t dataColor; + vec4_t nameColor; + const char* s; + float f; + + nameColor[3] = dataColor[3] = 1.0f; + + // Validate the score + if ( score->client < 0 || score->client >= cgs.maxclients ) + { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + // Convienience + ci = &cgs.clientinfo[score->client]; + + CG_DrawPic ( x - 5, y, w, sb_lineHeight, cgs.media.scoreboardLine ); + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) + { + vec4_t hcolor; + + hcolor[0] = 1.0f; + hcolor[1] = 1.0f; + hcolor[2] = 1.0f; + hcolor[3] = .10f; + + CG_FillRect( x - 5, y, w, sb_lineHeight, hcolor ); + + VectorSet ( nameColor, 1.0f, 1.0f, 1.0f ); + VectorSet ( dataColor, 0.5f, 0.5f, 0.5f ); + } + else if ( (cg.snap->ps.pm_type == PM_DEAD) && score->client == cg.snap->ps.persistant[PERS_ATTACKER] ) + { + vec4_t hcolor; + + hcolor[0] = 1.0f; + hcolor[1] = 1.0f; + hcolor[2] = 1.0f; + hcolor[3] = .10f; + + CG_FillRect( x - 5, y, w, sb_lineHeight, hcolor ); + + VectorCopy ( color, nameColor ); + VectorSet ( dataColor, 0.5f, 0.5f, 0.5f ); + } + else + { + VectorCopy ( color, nameColor ); + VectorSet ( dataColor, 0.3f, 0.3f, 0.3f ); + + if ( ci->ghost ) + { + VectorScale ( nameColor, 0.6f, nameColor ); + } + } + + CG_DrawText( x, y, cgs.media.hudFont, sb_nameFontScale, nameColor, ci->name, 24, DT_OUTLINE ); + + s = va("%i", score->score ); + f = trap_R_GetTextWidth ( s, cgs.media.hudFont, sb_nameFontScale, 0 ); + CG_DrawText( x + w - 10 - f, y, cgs.media.hudFont, sb_nameFontScale, nameColor, va("%i", score->score), 0, DT_OUTLINE ); + + // Draw skull if dead and not in intermission + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) + { + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) + { + vec3_t deadColor; + deadColor[0] = 0.60f; + deadColor[1] = 0.60f; + deadColor[2] = 0.60f; + deadColor[3] = 1.0f; + CG_DrawText( x + w - 70, y + 3, cgs.media.hudFont, sb_readyFontScale, deadColor, "READY", 0, DT_OUTLINE ); + } + } + else if ( ci->ghost ) + { + CG_DrawPic ( x + w - 70, y + 1, sb_numberFontScale * 0.8f, sb_numberFontScale * 0.8f, cgs.media.deadShader ); + } + // Draw any gametype items the guy is carrying + else + { + float xx = x + w - 70; + int i; + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + centity_t* cent; + + cent = CG_GetEntity ( score->client); + + // No have item, no draw it + if ( !(ci->gametypeitems & (1<kills, score->deaths); + f = trap_R_GetTextWidth ( s, cgs.media.hudFont, sb_readyFontScale, 0 ); + CG_DrawText( x + w - 10 - f, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, s, 0, 0 ); + + CG_DrawText( x, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("id: %i", score->client ), 0, 0 ); + CG_DrawText( x + 40, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("ping: %i", score->ping ), 0, 0 ); + CG_DrawText( x + 95, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("time: %i", score->time ), 0, 0 ); + + if ( score->teamkillDamage ) + { + CG_DrawText( x + 150, y + sb_numberFontScale, cgs.media.hudFont, sb_readyFontScale, dataColor, va("tk: %i%%", score->teamkillDamage ), 0, 0 ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( float x, float y, float w, team_t team ) +{ + int i; + int skipped; + float color[4]; + int count; + clientInfo_t *ci; + const char* s; + int players; + qboolean drawnClient; + + // Do we make sure the current client is drawn? + drawnClient = qtrue; + if ( cg.scores [ cg.snap->ps.clientNum ].team == team ) + { + drawnClient = qfalse; + } + + // Determine the color for this team + switch ( team ) + { + case TEAM_RED: + VectorCopy4 ( g_color_table[ColorIndex(COLOR_RED)], color ); + break; + + case TEAM_BLUE: + VectorCopy4 ( g_color_table[ColorIndex(COLOR_BLUE)], color ); + break; + + case TEAM_FREE: + VectorCopy4 ( g_color_table[ColorIndex(COLOR_GREEN)], color ); + break; + + case TEAM_SPECTATOR: + default: + VectorCopy4 ( colorWhite, color ); + break; + } + + // Draw as many clients as we can for this team + for ( skipped = -1, count = 0, i = 0 ; i < cg.numScores && count < sb_maxClients ; i++ ) + { + score_t* score; + + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != score->team ) + { + continue; + } + + if ( count == sb_maxClients - 1 && !drawnClient ) + { + if ( score->client != cg.snap->ps.clientNum ) + { + skipped = i; + continue; + } + + drawnClient = qtrue; + } + + CG_DrawClientScore( x, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, score, color ); + + count++; + } + + if ( skipped != -1 && count < sb_maxClients ) + { + CG_DrawClientScore( x, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, &cg.scores[skipped], color ); + + count++; + } + + s = ""; + switch ( team ) + { + case TEAM_RED: + s = "RED TEAM"; + players = ui_info_redcount.integer; + break; + + case TEAM_BLUE: + s = "BLUE TEAM"; + players = ui_info_bluecount.integer; + break; + + case TEAM_FREE: + s = "PLAYERS"; + players = ui_info_freecount.integer; + break; + + default: + case TEAM_SPECTATOR: + s = "SPECTATORS"; + players = ui_info_speccount.integer; + break; + } + + // Use the same team color here, but alpha it a bit. + color[3] = 0.6f; + + // Draw the header information for this team + CG_DrawPic ( x - 5, y, w, 25, cgs.media.scoreboardHeader ); + CG_FillRect( x - 5, y, w, 25, color ); + CG_DrawText ( x, y, cgs.media.hudFont, 0.40f, colorWhite, va("%s", s), 0, 0 ); + CG_DrawText ( x, y + 13, cgs.media.hudFont, 0.30f, colorWhite, va("players: %d", players), 0, 0 ); + + // Draw the totals if this is the red or blue team + if ( (team == TEAM_RED || team == TEAM_BLUE)) + { + const char* s; + float f; + + s = va("%d",(cg.teamScores[team-TEAM_RED])); + f = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ); + CG_DrawText ( x + w - 10 - f, y, cgs.media.hudFont, 0.43f, colorWhite, s, 0, DT_OUTLINE ); + } + + if ( count ) + { + CG_DrawPic ( x - 5, y + SB_HEADER_HEIGHT + sb_lineHeight * count, w, sb_lineHeight, cgs.media.scoreboardFooter ); + } + + y = count * sb_lineHeight + y + 10; + + if ( y > cg.scoreBoardBottom ) + { + cg.scoreBoardBottom = y; + } + + return y; +} + +/* +================= +CG_DrawNormalScoreboard + +Draws a scoreboard that has no teams +================= +*/ +qboolean CG_DrawNormalScoreboard ( float y ) +{ + cg.scoreBoardBottom = 0; + + // DRaw the game timer and the game type + CG_DrawText ( 385, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, "Game Time:", 0, DT_OUTLINE ); + CG_DrawTimer ( 455, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, DT_OUTLINE, cg.time - cgs.levelStartTime ); + CG_DrawText ( 150, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, cgs.gametypeData->displayName, 0, DT_OUTLINE ); + + if ( ui_info_speccount.integer ) + { + CG_FillRect ( 0, 470, 640, 10, colorBlack ); + CG_DrawText ( 5, 470, cgs.media.hudFont, 0.30f, colorWhite, va("SPECTATORS: %s", cg.scoreBoardSpectators), 0, 0 ); + } + + if ( ui_info_freecount.integer > 10 ) + { + sb_maxClients = 16; + sb_lineHeight = 20; + sb_nameFontScale = 0.35f; + sb_readyFontScale = 0.30f; + sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ); + } + else + { + sb_maxClients = 10; + sb_lineHeight = 30; + sb_nameFontScale = 0.43f; + sb_readyFontScale = 0.30f; + sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ) + 4; + } + + CG_TeamScoreboard ( 150, y, 350, TEAM_FREE ); + + return qtrue; +} + +/* +================= +CG_DrawTeamScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawTeamScoreboard( float y ) +{ + qboolean redFirst = qfalse; + + cg.scoreBoardBottom = 0; + + // DRaw the game timer and the game type + CG_DrawText ( 470, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, "Game Time:", 0, DT_OUTLINE ); + CG_DrawTimer ( 540, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, DT_OUTLINE, cg.time - cgs.levelStartTime ); + CG_DrawText ( 60, y - 14, cgs.media.hudFont, 0.38f, colorLtGrey, cgs.gametypeData->displayName, 0, DT_OUTLINE ); + + if ( ui_info_speccount.integer ) + { + CG_FillRect ( 0, 470, 640, 10, colorBlack ); + CG_DrawText ( 5, 470, cgs.media.hudFont, 0.30f, colorWhite, va("SPECTATORS: %s", cg.scoreBoardSpectators), 0, 0 ); + } + + if ( ui_info_redcount.integer > 10 || ui_info_bluecount.integer > 10 ) + { + sb_maxClients = 16; + sb_lineHeight = 20; + sb_nameFontScale = 0.35f; + sb_readyFontScale = 0.30f; + sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ); + } + else + { + sb_maxClients = 10; + sb_lineHeight = 30; + sb_nameFontScale = 0.43f; + sb_readyFontScale = 0.30f; + sb_numberFontScale = trap_R_GetTextHeight ( "W", cgs.media.hudFont, sb_nameFontScale, 0 ) + 4; + } + + // If there are more scores than the scoreboard can show then show the + // players team first rather than the winning team + if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR ) + { + if ( cg.teamScores[0] >= cg.teamScores[1] ) + { + redFirst = qtrue; + } + } + else if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + { + redFirst = qtrue; + } + + if ( redFirst ) + { + CG_TeamScoreboard( 50, y, 265, TEAM_RED ); + CG_TeamScoreboard( 330, y, 265, TEAM_BLUE ); + } + else + { + CG_TeamScoreboard( 330, y, 265, TEAM_RED ); + CG_TeamScoreboard( 50, y, 265, TEAM_BLUE ); + } + + return qtrue; +} + +/* +================= +CG_DrawScoreboard + +Draws either a team or a normal scoreboard +================= +*/ +qboolean CG_DrawScoreboard ( void ) +{ + float y; + float w; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) + { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) + { + return qfalse; + } + + if ( !cg.showScores && + cg.predictedPlayerState.pm_type != PM_DEAD && + cg.predictedPlayerState.pm_type != PM_INTERMISSION ) + { + return qfalse; + } + + // scoreboard + y = 45; + + if ( cgs.gametypeData->teams ) + { + if ( ui_info_redcount.integer < 10 && ui_info_bluecount.integer < 10 ) + { + y += 50; + } + } + else if ( ui_info_freecount.integer < 10 ) + { + y += 50; + } + + // Draw any gameover text + if ( cgs.gameover[0] ) + { + w = trap_R_GetTextWidth ( cgs.gameover, cgs.media.hudFont, 0.48f, 0 ); + CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, cgs.gameover, 0, DT_OUTLINE ); + } + else if ( cgs.gametypeMessageTime > cg.time ) + { + w = trap_R_GetTextWidth ( cgs.gametypeMessage, cgs.media.hudFont, 0.48f, 0 ); + CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, cgs.gametypeMessage, 0, DT_OUTLINE ); + } + // Should we draw who killed you? + else if ( cg.snap->ps.pm_type == PM_DEAD && + cg.snap->ps.persistant[PERS_ATTACKER] < MAX_CLIENTS && + cg.snap->ps.persistant[PERS_ATTACKER] != cg.snap->ps.clientNum ) + { + const char* s; + s = va("Killed by %s", cgs.clientinfo[cg.snap->ps.persistant[PERS_ATTACKER]].name ); + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.48f, 0 ); + CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, s, 0, DT_OUTLINE ); + } + else if ( cgs.gametypeData->teams ) + { + const char* s; + if ( cg.teamScores[0] == cg.teamScores[1] ) + { + s = va ( "Game Tied at %d", cg.teamScores[0] ); + } + else if ( cg.teamScores[0] > cg.teamScores[1] ) + { + s = va ( "Red leads Blue by %d", cg.teamScores[0] - cg.teamScores[1] ); + } + else + { + s = va ( "Blue leads Red by %d", cg.teamScores[1] - cg.teamScores[0] ); + } + + w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.48f, 0 ); + CG_DrawText ( 320 - w / 2, y - 30, cgs.media.hudFont, 0.48f, colorWhite, s, 0, DT_OUTLINE ); + } + + // load any models that have been deferred + cg.deferredPlayerLoading++; + + if ( cgs.gametypeData->teams ) + { + return CG_DrawTeamScoreboard ( y ); + } + + return CG_DrawNormalScoreboard ( y ); +} diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c new file mode 100644 index 0000000..554d761 --- /dev/null +++ b/code/cgame/cg_servercmds.c @@ -0,0 +1,1052 @@ +// Copyright (C) 2001-2002 Raven Software +// +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../../ui/menudef.h" +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +/* +================= +CG_ParseScores +================= +*/ +static void CG_ParseScores( void ) +{ + int i; + + cg.scoreBoardSpectators[0] = '\0'; + + cg.numScores = atoi( CG_Argv( 1 ) ); + if ( cg.numScores > MAX_CLIENTS ) + { + cg.numScores = MAX_CLIENTS; + } + + cg.teamScores[0] = atoi( CG_Argv( 2 ) ); + cg.teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + for ( i = 0 ; i < cg.numScores ; i++ ) + { + cg.scores[i].client = atoi( CG_Argv( i * 9 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 9 + 5 ) ); + cg.scores[i].kills = atoi( CG_Argv( i * 9 + 6 ) ); + cg.scores[i].deaths = atoi( CG_Argv( i * 9 + 7 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 9 + 8 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 9 + 9 ) ); + cgs.clientinfo[ cg.scores[i].client ].ghost = atoi( CG_Argv( i * 9 + 10 ) ); + cgs.clientinfo[ cg.scores[i].client ].gametypeitems = atoi( CG_Argv( i * 9 + 11 ) ); + cg.scores[i].teamkillDamage = atoi( CG_Argv( i * 9 + 12 ) ); + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) + { + cg.scores[i].client = 0; + } + + if ( cg.scores[i].ping < 0 ) + { + cg.scores[i].time = 0; + } + + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; + + if ( cg.scores[i].team == TEAM_SPECTATOR ) + { + if ( cg.scoreBoardSpectators[0] ) + { + strcat ( cg.scoreBoardSpectators, ", " ); + } + + strcat ( cg.scoreBoardSpectators, va("%s (%d)", cgs.clientinfo[cg.scores[i].client].name, cg.scores[i].ping ) ); + } + } +} + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) +{ + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = BG_FindGametype ( Info_ValueForKey( info, "g_gametype" ) ); + cgs.gametypeData = &bg_gametypeData[cgs.gametype]; + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.scorelimit = atoi( Info_ValueForKey( info, "scorelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + cgs.friendlyFire = atoi( Info_ValueForKey( info, "g_friendlyFire" ) ) ? qtrue : qfalse; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + + trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) ); + trap_Cvar_Set ( "ui_about_gametypename", cgs.gametypeData->displayName ); + trap_Cvar_Set ( "ui_about_scorelimit", va("%i", cgs.scorelimit ) ); + trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) ); + trap_Cvar_Set ( "ui_about_maxclients", va("%i", cgs.maxclients ) ); + trap_Cvar_Set ( "ui_about_dmflags", va("%i", cgs.dmflags ) ); + trap_Cvar_Set ( "ui_about_mapname", mapname ); + trap_Cvar_Set ( "ui_about_hostname", Info_ValueForKey( info, "sv_hostname" ) ); + trap_Cvar_Set ( "ui_about_needpass", Info_ValueForKey( info, "g_needpass" ) ); + trap_Cvar_Set ( "ui_about_botminplayers", Info_ValueForKey ( info, "bot_minplayers" ) ); + trap_Cvar_Set ( "ui_info_availableweapons", Info_ValueForKey ( info, "g_availableWeapons" ) ); + trap_Cvar_Set ( "ui_info_teamgame", va("%i", cgs.gametypeData->teams ? 1 : 0 ) ); + + BG_SetAvailableOutfitting ( Info_ValueForKey ( info, "g_availableWeapons" ) ); + + if ( cgs.gametypeData->teams ) + { + trap_Cvar_Set ( "ui_info_redteam", CG_ConfigString ( CS_GAMETYPE_REDTEAM ) ); + trap_Cvar_Set ( "ui_info_blueteam", CG_ConfigString ( CS_GAMETYPE_BLUETEAM ) ); + } + else + { + trap_Cvar_Set ( "ui_info_redteam", "" ); + trap_Cvar_Set ( "ui_info_blueteam", "" ); + } + + info = CG_ConfigString( CS_TERRAINS + 1 ); + if ( !info || !*info ) + { + cg.mInRMG = qfalse; + } + else + { + cg.mInRMG = qtrue; + } +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) +{ + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + cg.warmup = warmup; +} + +static void CG_ParseGametypeTimer ( void ) +{ + cgs.gametypeTimerTime = atoi( CG_ConfigString( CS_GAMETYPE_TIMER ) ); +} + +static void CG_ParseGametypeMessage ( void ) +{ + char temp[1024]; + char* comma; + + strcpy ( temp, CG_ConfigString( CS_GAMETYPE_MESSAGE ) ); + comma = strchr ( temp, ',' ); + if ( !comma ) + { + return; + } + + *(comma++) = '\0'; + + cgs.gametypeMessageTime = atoi ( temp ); + + // Silent gametype message + if ( *comma == '@' ) + { + strcpy ( cgs.gametypeMessage, comma + 1 ); + } + else + { + strcpy ( cgs.gametypeMessage, comma ); + Com_Printf ( "@%s\n", cgs.gametypeMessage ); + } +} + +/* +================ +CG_ParseVoteTime +================ +*/ +static void CG_ParseVoteTime ( void ) +{ + char temp[1024]; + char* comma; + const char *str; + + str = CG_ConfigString( CS_VOTE_TIME ); + + strcpy ( temp, str ); + comma = strchr ( temp, ',' ); + if ( !comma ) + { + cgs.voteTime = cgs.voteDuration = 0; + return; + } + *comma = 0; + + cgs.voteTime = atoi(str); + cgs.voteDuration = atoi(comma+1); + cgs.voteModified = qtrue; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) +{ + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); + cgs.pickupsDisabled = atoi( CG_ConfigString( CS_PICKUPSDISABLED ) ); + cgs.gameID = atoi( CG_ConfigString( CS_GAME_ID ) ); + + trap_Cvar_Set ( "ui_info_pickupsdisabled", va("%i", cgs.pickupsDisabled ) ); + + CG_ParseGametypeTimer ( ); + CG_ParseGametypeMessage ( ); + CG_ParseVoteTime ( ); +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged(void) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while (o && *o) { + n = strstr(o, "="); + if (n && *n) { + strncpy(originalShader, o, n-o); + originalShader[n-o] = 0; + n++; + t = strstr(n, ":"); + if (t && *t) { + strncpy(newShader, n, t-n); + newShader[t-n] = 0; + } else { + break; + } + t++; + o = strstr(t, "@"); + if (o) { + strncpy(timeOffset, t, o-t); + timeOffset[o-t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +/* +================ +CG_ConfigStringModified +================ +*/ +static void CG_ConfigStringModified( void ) +{ + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) + { + CG_StartMusic( qtrue ); + } + else if ( num == CS_SERVERINFO ) + { + CG_ParseServerinfo(); + } + else if ( num == CS_WARMUP ) + { + CG_ParseWarmup(); + } + else if ( num == CS_GAMETYPE_TIMER ) + { + CG_ParseGametypeTimer ( ); + } + else if ( num == CS_GAMETYPE_MESSAGE ) + { + CG_ParseGametypeMessage ( ); + } + else if ( num == CS_LEVEL_START_TIME ) + { + cgs.levelStartTime = atoi( str ); + } + else if ( num == CS_VOTE_TIME ) + { + CG_ParseVoteTime(); + } + else if ( num == CS_VOTE_NEEDED ) + { + cgs.voteNeeded = atoi( str ); + cgs.voteModified = qtrue; + } + else if ( num == CS_VOTE_YES ) + { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } + else if ( num == CS_VOTE_NO ) + { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } + else if ( num == CS_VOTE_STRING ) + { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_ANNOUNCER ); + } + else if ( num == CS_INTERMISSION ) + { + cg.intermissionStarted = atoi( str ); + } + else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) + { + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); + } + else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) + { + if ( str[0] != '*' ) + { + // player specific sounds don't register here + cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str ); + } + } + else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) + { + CG_NewClientInfo( num - CS_PLAYERS ); + } + else if ( num == CS_SHADERSTATE ) + { + CG_ShaderStateChanged(); + } + else if ( num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3)) + { + CG_SetLightstyle(num - CS_LIGHT_STYLES); + } + else if ( num >= CS_ICONS && num < CS_ICONS + MAX_ICONS ) + { + cgs.gameIcons[ num - CS_ICONS ] = trap_R_RegisterShaderNoMip ( str ); + } +} + + +/* +======================= +CG_AddChatText + +Adds chat text to the chat chat buffer +======================= +*/ +static void CG_AddChatText ( int client, const char *str ) +{ + int len; + char *p; + char *ls; + int lastcolor; + int chatHeight; + float w; + + if ( client >= 0 ) + { + cgs.clientinfo[client].mLastChatTime = cg.time; + } + + // Grab the users chat height settings + chatHeight = cg_chatHeight.integer; + if ( chatHeight > CHAT_HEIGHT ) + { + chatHeight = CHAT_HEIGHT; + } + + // Chats disabled? + if ( chatHeight <= 0 || cg_chatTime.integer <= 0 ) + { + cgs.chatPos = cgs.chatLastPos = 0; + return; + } + + len = 0; + + lastcolor = COLOR_WHITE; + + // Next position to write chat text too in the circular chat buffer + p = cgs.chatText[cgs.chatPos % chatHeight]; + *p = 0; + + ls = NULL; + w = 0; + + while (*str) + { + float cw = trap_R_GetTextWidth ( va("%c",*str), cgs.media.hudFont, 0.43f, 0 ); + + if ( w > 560 ) + { + w = 0; + + if (ls) + { + str -= (p - ls); + str++; + p -= (p - ls); + } + + *p = 0; + + cgs.chatTime[cgs.chatPos % chatHeight] = cg.time; + + cgs.chatPos++; + p = cgs.chatText[ cgs.chatPos % chatHeight ]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) + { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + + if (*str == ' ') + { + ls = p; + } + + *p++ = *str++; + len++; + w += cw; + } + *p = 0; + + cgs.chatTime[ cgs.chatPos % chatHeight] = cg.time; + cgs.chatPos++; + + if (cgs.chatPos - cgs.chatLastPos > chatHeight) + { + cgs.chatLastPos = cgs.chatPos - chatHeight; + } +} + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A map restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +void CG_MapRestart( qboolean gametypeRestart ) +{ + if ( cg_showmiss.integer ) + { + Com_Printf( "CG_MapRestart\n" ); + } + + trap_R_ClearDecals ( ); + trap_FX_Reset ( ); + trap_MAT_Reset(); + + CG_InitLocalEntities(); + + cg.intermissionStarted = qfalse; + + // dont clear votes on gametype restarts + if ( !gametypeRestart ) + { + cgs.voteTime = 0; + cgs.gametypeMessage[0] = '\0'; + cgs.gametypeMessageTime = 0; + } + + cgs.gameover[0] = '\0'; + + cg.mapRestart = qtrue; + + // Make sure the weapon selection menu isnt up + cg.weaponMenuUp = qfalse; + + cg.gametypeStarted = qfalse; + +// cgs.media.mAutomap = 0; // make sure to re-upload the auto-map + + CG_StartMusic(qtrue); + + trap_S_ClearLoopingSounds(qtrue); + + trap_Cvar_Set("cg_thirdPerson", "0"); +} + +#define MAX_VOICEFILESIZE 16384 +#define MAX_VOICEFILES 8 +#define MAX_VOICECHATS 64 +#define MAX_VOICESOUNDS 64 +#define MAX_CHATSIZE 64 +#define MAX_HEADMODELS 64 + +typedef struct voiceChat_s +{ + char id[64]; + int numSounds; + sfxHandle_t sounds[MAX_VOICESOUNDS]; + char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; +} voiceChat_t; + +typedef struct voiceChatList_s +{ + char name[64]; + int gender; + int numVoiceChats; + voiceChat_t voiceChats[MAX_VOICECHATS]; +} voiceChatList_t; + +typedef struct headModelVoiceChat_s +{ + char headmodel[64]; + int voiceChatNum; +} headModelVoiceChat_t; + +voiceChatList_t voiceChatLists[MAX_VOICEFILES]; +headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; + +/* +================= +CG_ParseVoiceChats +================= +*/ +int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + const char **p, *ptr; + char *token; + voiceChat_t *voiceChats; + qboolean compress; + + compress = qtrue; + if (cg_buildScript.integer) { + compress = qfalse; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); + return qfalse; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); + voiceChats = voiceChatList->voiceChats; + for ( i = 0; i < maxVoiceChats; i++ ) { + voiceChats[i].id[0] = 0; + } + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "female")) { + voiceChatList->gender = GENDER_FEMALE; + } + else if (!Q_stricmp(token, "male")) { + voiceChatList->gender = GENDER_MALE; + } + else if (!Q_stricmp(token, "neuter")) { + voiceChatList->gender = GENDER_NEUTER; + } + else { + trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); + return qfalse; + } + + voiceChatList->numVoiceChats = 0; + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); + token = COM_ParseExt(p, qtrue); + if (Q_stricmp(token, "{")) { + trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); + return qfalse; + } + voiceChats[voiceChatList->numVoiceChats].numSounds = 0; + while(1) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "}")) + break; + voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = + trap_S_RegisterSound( token ); + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ + voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); + voiceChats[voiceChatList->numVoiceChats].numSounds++; + if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) + break; + } + voiceChatList->numVoiceChats++; + if (voiceChatList->numVoiceChats >= maxVoiceChats) + return qtrue; + } + return qtrue; +} + +/* +================= +CG_LoadVoiceChats +================= +*/ +void CG_LoadVoiceChats( void ) +{ + int size; + + size = trap_MemoryRemaining(); + CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[1], MAX_VOICECHATS ); + + Com_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); +} + +/* +================= +CG_GetVoiceChat +================= +*/ +int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) +{ + int i, rnd; + + for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) + { + if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) + { + rnd = random() * voiceChatList->voiceChats[i].numSounds; + *snd = voiceChatList->voiceChats[i].sounds[rnd]; + *chat = voiceChatList->voiceChats[i].chats[rnd]; + return qtrue; + } + } + + return qfalse; +} + +/* +================= +CG_VoiceChatListForClient +================= +*/ +voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) +{ + clientInfo_t *ci; + + ci = &cgs.clientinfo[ clientNum ]; + + switch ( ci->gender ) + { + case GENDER_FEMALE: + return &voiceChatLists[0]; + + case GENDER_MALE: + return &voiceChatLists[1]; + } + + // just return the male voice chat list since there are more male characters + return &voiceChatLists[1]; +} + +#define MAX_VOICECHATBUFFER 32 + +typedef struct bufferedVoiceChat_s +{ + int clientNum; + sfxHandle_t snd; + int voiceOnly; + char cmd[MAX_SAY_TEXT]; + char message[MAX_SAY_TEXT]; +} bufferedVoiceChat_t; + +bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; + +/* +================= +CG_PlayVoiceChat +================= +*/ +void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) +{ + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) + { + return; + } + + if ( cg_voiceRadio.integer ) + { + trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); + } + + if (!vchat->voiceOnly && !cg_noVoiceText.integer) + { + CG_AddChatText ( vchat->clientNum, vchat->message ); + } + + voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; +} + +/* +===================== +CG_PlayBufferedVoieChats +===================== +*/ +void CG_PlayBufferedVoiceChats( void ) +{ + if ( cg.voiceChatTime < cg.time ) + { + if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) + { + // + CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); + // + cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; + cg.voiceChatTime = cg.time + 1000; + } + } +} + +/* +===================== +CG_AddBufferedVoiceChat +===================== +*/ +void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) +{ + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) + { + return; + } + + memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); + cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; + if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) + { + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + cg.voiceChatBufferOut++; + } +} + +/* +================= +CG_VoiceChatLocal +================= +*/ +void CG_VoiceChatLocal( qboolean voiceOnly, int clientNum, const char* chatprefix, const char *cmd ) +{ + char *chat; + voiceChatList_t *voiceChatList; + sfxHandle_t snd; + bufferedVoiceChat_t vchat; + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) + { + return; + } + + // Get the voice chat info for the speaking client + voiceChatList = CG_VoiceChatListForClient( clientNum ); + if ( !CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) + { + return; + } + + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.voiceOnly = voiceOnly; + Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); + + Com_sprintf ( vchat.message, sizeof(vchat.message), "%s%s", chatprefix, chat ); + + CG_AddBufferedVoiceChat(&vchat); +} + +/* +================= +CG_VoiceChat +================= +*/ +void CG_VoiceChat( int mode ) +{ + char cmd[MAX_QPATH]; + const char *chatprefix; + qboolean voiceOnly; + int clientNum; + + voiceOnly = atoi(CG_Argv(1)); + clientNum = atoi(CG_Argv(2)); + Com_sprintf ( cmd, MAX_QPATH, CG_Argv(4) ); + chatprefix = CG_Argv(3); + + CG_VoiceChatLocal ( voiceOnly, clientNum, chatprefix, cmd ); +} + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (text[i] == '\x19') + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + if ( !strcmp( cmd, "cp" ) ) + { + const char* s; + + s = CG_Argv(1); + if ( *s == '@' ) + { + s++; + } + else + { + Com_Printf ( "@%s", s ); + } + + CG_CenterPrint( s, 0.43f ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) + { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) + { + Com_Printf( "%s", CG_Argv(1) ); + +/* + cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about + // votes passing or failing + if ( !Q_stricmpn( cmd, "vote failed", 11 ) ) + { + trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); + } + else if ( !Q_stricmpn( cmd, "vote passed", 11 ) ) + { + trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); + } +*/ + return; + } + + if ( !strcmp( cmd, "chat" ) ) + { + if ( !cg_teamChatsOnly.integer ) + { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(2), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddChatText ( atoi(CG_Argv(1)), text ); + Com_Printf( "@%s\n", text ); + } + return; + } + + if ( !strcmp( cmd, "tchat" ) ) + { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(2), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddChatText ( atoi(CG_Argv(1)), text ); + Com_Printf( "@%s\n", text ); + return; + } + + if ( !strcmp( cmd, "vglobal" ) ) + { + char *chat; + voiceChatList_t *voiceChatList; + sfxHandle_t snd; + int clientNum; + + if ( !cg_voiceGlobal.integer ) + { + return; + } + + clientNum = atoi(CG_Argv(1)); + + // Get the voice chat info for the speaking client + voiceChatList = CG_VoiceChatListForClient( clientNum ); + if ( !CG_GetVoiceChat( voiceChatList, CG_Argv(2), &snd, &chat ) ) + { + return; + } + + trap_S_StartSound ( NULL, clientNum, CHAN_AUTO, snd, 240, 1150 ); + return; + } + + if ( !strcmp( cmd, "vtchat" ) ) + { + CG_VoiceChat( SAY_TEAM ); + return; + } + + if ( !strcmp( cmd, "vtell" ) ) + { + CG_VoiceChat( SAY_TELL ); + return; + } + + if ( !strcmp( cmd, "scores" ) ) + { + CG_ParseScores(); + CG_UpdateTeamCountCvars ( ); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) + { + CG_MapRestart( qfalse ); + return; + } + + if ( Q_stricmp (cmd, "remapShader") == 0 ) + { + if (trap_Argc() == 4) + { + trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddeferred" ) ) + { + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) + { + cg.levelShot = qtrue; + return; + } + + Com_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c new file mode 100644 index 0000000..b52ece9 --- /dev/null +++ b/code/cgame/cg_snapshot.c @@ -0,0 +1,503 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) +{ + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) + { + cent->previousEvent = 0; + } + + cent->trailTime = cg.snap->serverTime; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + if ( cent->currentState.eType == ET_PLAYER ) + { + CG_ResetPlayerEntity( cent ); + } + + cent->pe.weapon = 0; +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) +{ + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // Time is overloaded for players to store the spawn count, so when a player is newly spawned + // their gore should be reset + if ( cent->pe.spawnCount != cent->currentState.time ) + { + cent->pe.spawnCount = cent->currentState.time; + + // Force all the animations to be restarted + cent->pe.torso.anim = -1; + cent->pe.legs.anim = -1; + + if ( cent->ghoul2 ) + { + trap_G2API_ClearSkinGore ( cent->ghoul2 ); + } + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on map restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) +{ + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + cent = CG_GetEntity ( snap->ps.clientNum ); + + if (!cgs.clientinfo[snap->ps.clientNum].ghoul2Model) + { + Com_Error(ERR_DROP, "CG_SetInitialSnapshot invalid g2 pointer for client\n"); + } + + if ((cent->ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[snap->ps.clientNum].ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[snap->ps.clientNum].ghoul2Model, ¢->ghoul2); + } + + BG_PlayerStateToEntityState( &snap->ps, ¢->currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) + { + state = &cg.snap->entities[ i ]; + cent = CG_GetEntity ( state->number ); + + memcpy(¢->currentState, state, sizeof(entityState_t)); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) +{ + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) + { + Com_Error( ERR_FATAL, "CG_TransitionSnapshot: NULL cg.snap" ); + } + + if ( !cg.nextSnap ) + { + Com_Error( ERR_FATAL, "CG_TransitionSnapshot: NULL cg.nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if ( !cg.snap ) + { + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) + { + cent = CG_GetEntity ( cg.snap->entities[ i ].number ); + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + cent = CG_GetEntity ( cg.snap->ps.clientNum ); + BG_PlayerStateToEntityState( &cg.snap->ps, ¢->currentState, qfalse ); + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) + { + // Transition the entity + cent = CG_GetEntity ( cg.snap->entities[ i ].number ); + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) + { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) + { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer ) + { + CG_TransitionPlayerState( ps, ops ); + } + } +} + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) +{ + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + cent = CG_GetEntity ( snap->ps.clientNum ); + + BG_PlayerStateToEntityState( &snap->ps, ¢->nextState, qfalse ); + cent->interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) + { + es = &snap->entities[num]; + cent = CG_GetEntity ( es->number ); + + memcpy(¢->nextState, es, sizeof(entityState_t)); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) + { + cent->interpolate = qfalse; + } + else + { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) + { + cg.nextFrameTeleport = qtrue; + } + else + { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) + { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) + { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + Com_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + + /* + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + */ + dest = &cg.activeSnapshots[cg.activeSnapshot%3]; + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg.nextSnap && r && dest->serverTime == cg.nextSnap->serverTime ) { +// r = r; +// continue; + } + + // if it succeeded, return + if ( r ) { + cg.activeSnapshot++; + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) +{ + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + if ( n != cg.latestSnapshotNum ) + { + if ( n < cg.latestSnapshotNum ) + { + // this should never happen + Com_Error( ERR_FATAL, "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while ( !cg.snap ) + { + snap = CG_ReadNextSnapshot(); + + if ( !snap ) + { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) + { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do + { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap || cg.needNextSnap ) // !cg.nextSnap ) + { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) + { + if ( cg_drawSnapshot.integer ) + Com_Printf ( "NOSNAP\n" ); + + + break; + } + +#ifdef _SNAPSHOT_EXTRAPOLATION + + cg.needNextSnap = qfalse; + + if ( cg.nextSnap) + { + if ( cg_drawSnapshot.integer ) + Com_Printf ( "TRANS\n" ); + + CG_TransitionSnapshot(); + } +#endif + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) + { + Com_Error( ERR_FATAL, "CG_ProcessSnapshots: Server time went backwards" ); + } + } + +#ifdef _SNAPSHOT_EXTRAPOLATION + + if ( cg.needNextSnap ) + { + if ( cg_drawSnapshot.integer ) + Com_Printf ( "NEED\n" ); + + break; + } + +#endif + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) + { + break; + } + +#ifdef _SNAPSHOT_EXTRAPOLATION + + cg.needNextSnap = qtrue; + + if ( cg_drawSnapshot.integer ) + Com_Printf ( "SNAP: time=%i\n", cg.time ); + +#else + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); +#endif + + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) + { + Com_Error( ERR_FATAL, "CG_ProcessSnapshots: cg.snap == NULL" ); + } + + if ( cg.time < cg.snap->serverTime ) + { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + +#ifndef _SNAPSHOT_EXTRAPOLATION + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) + { + Com_Error( ERR_FATAL, "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } +#endif +} diff --git a/code/cgame/cg_syscalls.asm b/code/cgame/cg_syscalls.asm new file mode 100644 index 0000000..977f4a4 --- /dev/null +++ b/code/cgame/cg_syscalls.asm @@ -0,0 +1,229 @@ +code + +equ trap_Print -1 ; CG_PRINT +equ trap_Error -2 ; CG_ERROR +equ trap_Milliseconds -3 ; CG_MILLISECONDS +equ trap_Cvar_Register -4 ; CG_CVAR_REGISTER +equ trap_Cvar_Update -5 ; CG_CVAR_UPDATE +equ trap_Cvar_Set -6 ; CG_CVAR_SET +equ trap_Cvar_VariableStringBuffer -7 ; CG_CVAR_VARIABLESTRINGBUFFER +equ trap_Argc -8 ; CG_ARGC +equ trap_Argv -9 ; CG_ARGV +equ trap_Args -10 ; CG_ARGS +equ trap_FS_FOpenFile -11 ; CG_FS_FOPENFILE +equ trap_FS_Read -12 ; CG_FS_READ +equ trap_FS_Write -13 ; CG_FS_WRITE +equ trap_FS_FCloseFile -14 ; CG_FS_FCLOSEFILE +equ trap_SendConsoleCommand -16 ; CG_SENDCONSOLECOMMAND +equ trap_AddCommand -17 ; CG_ADDCOMMAND +equ trap_RemoveCommand -121 ; CG_REMOVECOMMAND +equ trap_SendClientCommand -18 ; CG_SENDCLIENTCOMMAND +equ trap_UpdateScreen -19 ; CG_UPDATESCREEN +equ trap_RMG_Init -20 ; CG_RMG_INIT +equ trap_CM_LoadMap -21 ; CG_CM_LOADMAP +equ trap_CM_NumInlineModels -22 ; CG_CM_NUMINLINEMODELS +equ trap_CM_InlineModel -23 ; CG_CM_INLINEMODEL +equ trap_CM_TempBoxModel -25 ; CG_CM_TEMPBOXMODEL +equ trap_CM_TempCapsuleModel -134 ; CG_CM_TEMPCAPSULEMODEL +equ trap_CM_PointContents -26 ; CG_CM_POINTCONTENTS +equ trap_CM_TransformedPointContents -27 ; CG_CM_TRANSFORMEDPOINTCONTENTS +equ trap_CM_BoxTrace -28 ; CG_CM_BOXTRACE +equ trap_CM_CapsuleTrace -135 ; CG_CM_CAPSULETRACE +equ trap_CM_TransformedBoxTrace -29 ; CG_CM_TRANSFORMEDBOXTRACE +equ trap_CM_TransformedCapsuleTrace -136 ; CG_CM_TRANSFORMEDCAPSULETRACE +equ trap_CM_MarkFragments -30 ; CG_CM_MARKFRAGMENTS +equ trap_S_StopAllSounds -32 ; CG_S_STOPALLSOUNDS +equ trap_S_StartSound -31 ; CG_S_STARTSOUND +equ trap_S_StartLocalSound -33 ; CG_S_STARTLOCALSOUND +equ trap_S_ClearLoopingSounds -34 ; CG_S_CLEARLOOPINGSOUNDS +equ trap_S_AddLoopingSound -35 ; CG_S_ADDLOOPINGSOUND +equ trap_S_AddRealLoopingSound -132 ; CG_S_ADDREALLOOPINGSOUND +equ trap_S_StopLoopingSound -133 ; CG_S_STOPLOOPINGSOUND +equ trap_S_UpdateEntityPosition -36 ; CG_S_UPDATEENTITYPOSITION +equ trap_S_Respatialize -37 ; CG_S_RESPATIALIZE +equ trap_S_RegisterSound -38 ; CG_S_REGISTERSOUND +equ trap_S_StartBackgroundTrack -39 ; CG_S_STARTBACKGROUNDTRACK +equ trap_AS_AddPrecacheEntry -40 ; CG_AS_ADDPRECACHEENTRY +equ trap_AS_ParseSets -41 ; CG_AS_PARSESETS +equ trap_AS_UpdateAmbientSet -42 ; CG_AS_UPDATEAMBIENTSET +equ trap_AS_AddLocalSet -43 ; CG_AS_ADDLOCALSET +equ trap_AS_GetBModelSound -44 ; CG_AS_GETBMODELSOUND +equ trap_R_LoadWorldMap -45 ; CG_R_LOADWORLDMAP +equ trap_R_RegisterModel -46 ; CG_R_REGISTERMODEL +equ trap_R_RegisterSkin -47 ; CG_R_REGISTERSKIN +equ trap_R_RegisterShader -48 ; CG_R_REGISTERSHADER +equ trap_R_RegisterShaderNoMip -78 ; CG_R_REGISTERSHADERNOMIP +equ trap_R_RegisterFont -80 ; CG_R_REGISTERFONT +equ trap_R_ClearScene -49 ; CG_R_CLEARSCENE +equ trap_R_ClearDecals -50 ; CG_R_CLEARDECALS +equ trap_R_AddRefEntityToScene -51 ; CG_R_ADDREFENTITYTOSCENE +equ trap_R_AddPolyToScene -52 ; CG_R_ADDPOLYTOSCENE +equ trap_R_AddDecalToScene -53 ; CG_R_ADDDECALTOSCENE +equ trap_R_AddPolysToScene -139 ; CG_R_ADDPOLYSTOSCENE +equ trap_R_LightForPoint -122 ; CG_R_LIGHTFORPOINT +equ trap_R_AddLightToScene -54 ; CG_R_ADDLIGHTTOSCENE +equ trap_R_AddAdditiveLightToScene -137 ; CG_R_ADDADDITIVELIGHTTOSCENE +equ trap_R_RenderScene -55 ; CG_R_RENDERSCENE +equ trap_R_DrawVisualOverlay -56 ; CG_R_DRAWVISUALOVERLAY +equ trap_R_SetColor -57 ; CG_R_SETCOLOR +equ trap_R_DrawStretchPic -58 ; CG_R_DRAWSTRETCHPIC +equ trap_R_ModelBounds -59 ; CG_R_MODELBOUNDS +equ trap_R_LerpTag -60 ; CG_R_LERPTAG +equ trap_R_DrawRotatePic -61 ; CG_R_DRAWROTATEPIC +equ trap_R_DrawRotatePic2 -62 ; CG_R_DRAWROTATEPIC2 +equ trap_R_RemapShader -128 ; CG_R_REMAP_SHADER +equ trap_R_GetLightStyle -129 ; CG_R_GET_LIGHT_STYLE +equ trap_R_SetLightStyle -130 ; CG_R_SET_LIGHT_STYLE +equ trap_R_GetTextWidth -65 ; CG_R_GETTEXTWIDTH +equ trap_R_GetTextHeight -66 ; CG_R_GETTEXTHEIGHT +equ trap_R_DrawText -63 ; CG_R_DRAWTEXT +equ trap_R_DrawTextWithCursor -64 ; CG_R_DRAWTEXTWITHCURSOR +equ trap_FX_AddLine -131 ; CG_FX_ADDLINE +equ trap_GetGlconfig -67 ; CG_GETGLCONFIG +equ trap_GetGameState -68 ; CG_GETGAMESTATE +equ trap_GetCurrentSnapshotNumber -69 ; CG_GETCURRENTSNAPSHOTNUMBER +equ trap_GetSnapshot -70 ; CG_GETSNAPSHOT +equ trap_GetDefaultState -71 ; CG_GETDEFAULTSTATE +equ trap_GetServerCommand -72 ; CG_GETSERVERCOMMAND +equ trap_GetCurrentCmdNumber -73 ; CG_GETCURRENTCMDNUMBER +equ trap_GetUserCmd -74 ; CG_GETUSERCMD +equ trap_SetUserCmdValue -75 ; CG_SETUSERCMDVALUE +equ trap_RW_SetTeam -76 ; CG_RW_SETTEAM +equ trap_ResetAutorun -77 ; CG_RESETAUTORUN +equ trap_MemoryRemaining -79 ; CG_MEMORY_REMAINING +equ trap_Key_IsDown -81 ; CG_KEY_ISDOWN +equ trap_Key_GetCatcher -82 ; CG_KEY_GETCATCHER +equ trap_Key_SetCatcher -83 ; CG_KEY_SETCATCHER +equ trap_Key_GetKey -84 ; CG_KEY_GETKEY +equ trap_PC_AddGlobalDefine -85 ; CG_PC_ADD_GLOBAL_DEFINE +equ trap_PC_LoadSource -86 ; CG_PC_LOAD_SOURCE +equ trap_PC_FreeSource -87 ; CG_PC_FREE_SOURCE +equ trap_PC_ReadToken -88 ; CG_PC_READ_TOKEN +equ trap_PC_SourceFileAndLine -89 ; CG_PC_SOURCE_FILE_AND_LINE +equ trap_PC_LoadGlobalDefines -90 ; CG_PC_LOAD_GLOBAL_DEFINES +equ trap_PC_RemoveAllGlobalDefines -91 ; CG_PC_REMOVE_ALL_GLOBAL_DEFINES +equ trap_S_StopBackgroundTrack -118 ; CG_S_STOPBACKGROUNDTRACK +equ trap_RealTime -119 ; CG_REAL_TIME +equ trap_SnapVector -120 ; CG_SNAPVECTOR +equ trap_CIN_PlayCinematic -123 ; CG_CIN_PLAYCINEMATIC +equ trap_CIN_StopCinematic -124 ; CG_CIN_STOPCINEMATIC +equ trap_CIN_RunCinematic -125 ; CG_CIN_RUNCINEMATIC +equ trap_CIN_DrawCinematic -126 ; CG_CIN_DRAWCINEMATIC +equ trap_CIN_SetExtents -127 ; CG_CIN_SETEXTENTS +equ trap_GetEntityToken -138 ; CG_GET_ENTITY_TOKEN +equ trap_R_inPVS -140 ; CG_R_INPVS +equ trap_FX_RegisterEffect -141 ; CG_FX_REGISTER_EFFECT +equ trap_FX_PlaySimpleEffect -142 ; CG_FX_PLAY_SIMPLE_EFFECT +equ trap_FX_PlayEffect -143 ; CG_FX_PLAY_EFFECT +equ trap_FX_PlayEntityEffect -144 ; CG_FX_PLAY_ENTITY_EFFECT +equ trap_FX_PlaySimpleEffectID -145 ; CG_FX_PLAY_SIMPLE_EFFECT_ID +equ trap_FX_PlayEffectID -146 ; CG_FX_PLAY_EFFECT_ID +equ trap_FX_PlayEntityEffectID -147 ; CG_FX_PLAY_ENTITY_EFFECT_ID +equ trap_FX_PlayBoltedEffectID -148 ; CG_FX_PLAY_BOLTED_EFFECT_ID +equ trap_FX_AddScheduledEffects -149 ; CG_FX_ADD_SCHEDULED_EFFECTS +equ trap_FX_Draw2DEffects -153 ; CG_FX_DRAW_2D_EFFECTS +equ trap_FX_InitSystem -150 ; CG_FX_INIT_SYSTEM +equ trap_FX_FreeSystem -151 ; CG_FX_FREE_SYSTEM +equ trap_FX_Reset -154 ; CG_FX_RESET +equ trap_FX_AdjustTime -152 ; CG_FX_ADJUST_TIME +equ trap_G2_ListModelSurfaces -156 ; CG_G2_LISTSURFACES +equ trap_G2_ListModelBones -155 ; CG_G2_LISTBONES +equ trap_G2_SetGhoul2ModelIndexes -163 ; CG_G2_SETMODELS +equ trap_G2API_CollisionDetect -190 ; CG_G2_COLLISIONDETECT +equ trap_G2API_AddBolt -157 ; CG_G2_ADDBOLT +equ trap_G2API_SetBoltInfo -158 ; CG_G2_SETBOLTON +equ trap_G2API_RemoveBolt -159 ; CG_G2_REMOVEBOLT +equ trap_G2API_AttachG2Model -160 ; CG_G2_ATTACHG2MODEL +equ trap_G2API_DetachG2Model -161 ; CG_G2_DETACHG2MODEL +equ trap_G2_HaveWeGhoul2Models -162 ; CG_G2_HAVEWEGHOULMODELS +equ trap_G2API_GetBoltMatrix -164 ; CG_G2_GETBOLT +equ trap_G2API_InitGhoul2Model -165 ; CG_G2_INITGHOUL2MODEL +equ trap_G2API_GetAnimFileNameIndex -185 ; CG_G2_GETANIMFILENAMEINDEX +equ trap_G2API_RegisterSkin -188 ; CG_G2_REGISTERSKIN +equ trap_G2API_SetSkin -189 ; CG_G2_SETSKIN +equ trap_G2API_CleanGhoul2Models -166 ; CG_G2_CLEANMODELS +equ trap_G2API_SetBoneAngles -167 ; CG_G2_ANGLEOVERRIDE +equ trap_G2API_SetBoneAnim -168 ; CG_G2_PLAYANIM +equ trap_G2API_GetBoneAnim -169 ; CG_G2_GETANIM +equ trap_G2API_SetSurfaceOnOff -170 ; CG_G2_SETSURFACEONOFF +equ trap_G2API_SetRootSurface -171 ; CG_G2_SETROOTSURFACE +equ trap_G2API_SetNewOrigin -172 ; CG_G2_SETNEWORIGIN +equ trap_G2API_GetGLAName -173 ; CG_G2_GETGLANAME +equ trap_G2API_CopyGhoul2Instance -174 ; CG_G2_COPYGHOUL2INSTANCE +equ trap_G2API_CopySpecificGhoul2Model -175 ; CG_G2_COPYSPECIFICGHOUL2MODEL +equ trap_G2API_DuplicateGhoul2Instance -176 ; CG_G2_DUPLICATEGHOUL2INSTANCE +equ trap_G2API_RemoveGhoul2Model -177 ; CG_G2_REMOVEGHOUL2MODEL +equ trap_G2API_AddSkinGore -178 ; CG_G2_ADDSKINGORE +equ trap_G2API_ClearSkinGore -179 ; CG_G2_CLEARSKINGORE +equ trap_G2API_SetGhoul2ModelFlags -180 ; CG_G2_SETGHOUL2MODELFLAGS +equ trap_G2API_GetGhoul2ModelFlags -181 ; CG_G2_GETGHOUL2MODELFLAGS +equ trap_G2API_SetGhoul2ModelFlagsByIndex -182 ; CG_G2_SETGHOUL2MODELFLAGSBYINDEX +equ trap_G2API_GetGhoul2ModelFlagsByIndex -183 ; CG_G2_GETGHOUL2MODELFLAGSBYINDEX +equ trap_G2API_GetNumModels -184 ; CG_G2_GETNUMMODELS +equ trap_G2API_FindBoltIndex -186 ; CG_G2_FINDBOLTINDEX +equ trap_G2API_GetBoltIndex -187 ; CG_G2_GETBOLTINDEX +equ trap_MAT_Init -192 ; CG_MAT_CACHE +equ trap_MAT_Reset -191 ; CG_MAT_RESET +equ trap_MAT_GetSound -193 ; CG_MAT_GET_SOUND +equ trap_MAT_GetDecal -194 ; CG_MAT_GET_DECAL +equ trap_MAT_GetDecalScale -195 ; CG_MAT_GET_DECAL_SCALE +equ trap_MAT_GetEffect -196 ; CG_MAT_GET_EFFECT +equ trap_MAT_GetDebris -197 ; CG_MAT_GET_DEBRIS +equ trap_MAT_GetDebrisScale -198 ; CG_MAT_GET_DEBRIS_SCALE +equ trap_GP_Parse -205 ; GP_PARSE +equ trap_GP_ParseFile -206 ; GP_PARSE_FILE +equ trap_GP_Clean -207 ; GP_CLEAN +equ trap_GP_Delete -208 ; GP_DELETE +equ trap_GP_GetBaseParseGroup -209 ; GP_GET_BASE_PARSE_GROUP +equ trap_GPG_GetName -210 ; GPG_GET_NAME +equ trap_GPG_GetNext -211 ; GPG_GET_NEXT +equ trap_GPG_GetInOrderNext -212 ; GPG_GET_INORDER_NEXT +equ trap_GPG_GetInOrderPrevious -213 ; GPG_GET_INORDER_PREVIOUS +equ trap_GPG_GetPairs -214 ; GPG_GET_PAIRS +equ trap_GPG_GetInOrderPairs -215 ; GPG_GET_INORDER_PAIRS +equ trap_GPG_GetSubGroups -216 ; GPG_GET_SUBGROUPS +equ trap_GPG_GetInOrderSubGroups -217 ; GPG_GET_INORDER_SUBGROUPS +equ trap_GPG_FindSubGroup -218 ; GPG_FIND_SUBGROUP +equ trap_GPG_FindPair -219 ; GPG_FIND_PAIR +equ trap_GPG_FindPairValue -220 ; GPG_FIND_PAIRVALUE +equ trap_GPV_GetName -221 ; GPV_GET_NAME +equ trap_GPV_GetNext -222 ; GPV_GET_NEXT +equ trap_GPV_GetInOrderNext -223 ; GPV_GET_INORDER_NEXT +equ trap_GPV_GetInOrderPrevious -224 ; GPV_GET_INORDER_PREVIOUS +equ trap_GPV_IsList -225 ; GPV_IS_LIST +equ trap_GPV_GetTopValue -226 ; GPV_GET_TOP_VALUE +equ trap_GPV_GetList -227 ; GPV_GET_LIST +equ trap_CM_TM_Create -199 ; CG_CM_TM_CREATE +equ trap_CM_TM_AddBuilding -200 ; CG_CM_TM_ADDBUILDING +equ trap_CM_TM_AddSpot -201 ; CG_CM_TM_ADDSPOT +equ trap_CM_TM_AddTarget -202 ; CG_CM_TM_ADDTARGET +equ trap_CM_TM_Upload -203 ; CG_CM_TM_UPLOAD +equ trap_CM_TM_ConvertPosition -204 ; CG_CM_TM_CONVERT_POS +equ trap_CM_RegisterTerrain -228 ; CG_CM_REGISTER_TERRAIN +equ trap_RE_InitRendererTerrain -229 ; CG_RE_INIT_RENDERER_TERRAIN +equ trap_CG_RegisterSharedMemory -230 ; CG_SET_SHARED_BUFFER +equ trap_FS_GetFileList -15 ; CG_FS_GETFILELIST +equ trap_VM_LocalAlloc -231 ; CG_VM_LOCALALLOC +equ trap_VM_LocalAllocUnaligned -232 ; CG_VM_LOCALALLOCUNALIGNED +equ trap_VM_LocalTempAlloc -233 ; CG_VM_LOCALTEMPALLOC +equ trap_VM_LocalTempFree -234 ; CG_VM_LOCALTEMPFREE +equ trap_VM_LocalStringAlloc -235 ; CG_VM_LOCALSTRINGALLOC +equ trap_UI_CloseAll -237 ; CG_UI_CLOSEALL +equ trap_UI_SetActiveMenu -236 ; CG_UI_SETACTIVEMENU + + +; hardcoded functions +equ memset -101 ; CG_MEMSET +equ memcpy -102 ; CG_MEMCPY +equ strncpy -103 ; CG_STRNCPY +equ sin -104 ; CG_SIN +equ cos -105 ; CG_COS +equ atan2 -106 ; CG_ATAN2 +equ sqrt -107 ; CG_SQRT +equ matrixmultiply -116 ; CG_MATRIXMULTIPLY +equ anglevectors -108 ; CG_ANGLEVECTORS +equ perpendicularvector -109 ; CG_PERPENDICULARVECTOR +equ floor -110 ; CG_FLOOR +equ ceil -111 ; CG_CEIL +equ acos -114 ; CG_ACOS +equ asin -115 ; CG_ASIN diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c new file mode 100644 index 0000000..b616463 --- /dev/null +++ b/code/cgame/cg_syscalls.c @@ -0,0 +1,1056 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#include "cg_local.h" + +static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; + + +void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { + syscall = syscallptr; +} + + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags, float MinValue, float MaxValue ) +{ + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags, PASSFLOAT(MinValue), PASSFLOAT(MaxValue) ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) { + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_RMG_Init(int terrainID, const char *terrainInfo) +{ + syscall(CG_RMG_INIT, terrainID, terrainInfo); +} + +void trap_CM_LoadMap( const char *mapname, qboolean SubBSP ) { + syscall( CG_CM_LOADMAP, mapname, SubBSP ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StopAllSounds ( void ) +{ + syscall( CG_S_STOPALLSOUNDS ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx, int volume, int radius ) +{ + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx, volume, radius ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) { + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, float radius, sfxHandle_t sfx ) +{ + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, PASSFLOAT(radius), sfx ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, float radius, sfxHandle_t sfx ) +{ + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, PASSFLOAT(radius), sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + return syscall( CG_S_REGISTERSOUND, sample ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop, bReturnWithoutStarting ); +} + +void trap_AS_AddPrecacheEntry(const char *set) +{ + syscall( CG_AS_ADDPRECACHEENTRY, set ); +} + +void trap_AS_ParseSets(void) +{ + syscall( CG_AS_PARSESETS ); +} + +void trap_AS_UpdateAmbientSet (const char *name, vec3_t origin) +{ + syscall( CG_AS_UPDATEAMBIENTSET, name, origin ); +} + +int trap_AS_AddLocalSet(const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time) +{ + return syscall( CG_AS_ADDLOCALSET, name, listener_origin, origin, entID, time ); +} + +sfxHandle_t trap_AS_GetBModelSound(const char *name, int stage) +{ + return (sfxHandle_t)syscall( CG_AS_GETBMODELSOUND, name, stage ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +qhandle_t trap_R_RegisterFont(const char *fontName) +{ + return (qhandle_t)syscall(CG_R_REGISTERFONT, fontName ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_ClearDecals ( void ) +{ + syscall ( CG_R_CLEARDECALS ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ) +{ + syscall( CG_R_ADDDECALTOSCENE, shader, origin, dir, PASSFLOAT(orientation), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b), PASSFLOAT(a), alphaFade, PASSFLOAT(radius), temporary ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_DrawVisualOverlay ( visual_t type, qboolean preProcess, float parm1, float parm2 ) +{ + syscall( CG_R_DRAWVISUALOVERLAY, type, preProcess, PASSFLOAT(parm1), PASSFLOAT(parm2) ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, const float* color, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), color, hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_DrawRotatePic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +void trap_R_DrawRotatePic2( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC2, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_R_GetLightStyle(int style, color4ub_t color) +{ + syscall( CG_R_GET_LIGHT_STYLE, style, color ); +} + +void trap_R_SetLightStyle(int style, int color) +{ + syscall( CG_R_SET_LIGHT_STYLE, style, color ); +} + +int trap_R_GetTextWidth ( const char* text, qhandle_t font, float scale, int limit ) +{ + return syscall ( CG_R_GETTEXTWIDTH, text, font, PASSFLOAT(scale), limit ); +} + +int trap_R_GetTextHeight ( const char* text, qhandle_t font, float scale, int limit ) +{ + return syscall ( CG_R_GETTEXTHEIGHT, text, font, PASSFLOAT(scale), limit ); +} + +void trap_R_DrawText ( int x, int y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags ) +{ + syscall ( CG_R_DRAWTEXT, x, y, font, PASSFLOAT(scale), color, text, limit, flags ); +} + +void trap_R_DrawTextWithCursor ( int x, int y, qhandle_t font, float scale, vec4_t color, const char* text, int limit, int flags, int cursorPos, char cursor ) +{ + syscall ( CG_R_DRAWTEXTWITHCURSOR, x, y, font, PASSFLOAT(scale), color, text, limit, flags, cursorPos, cursor ); +} + +void trap_FX_AddLine( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags) +{ + syscall( CG_FX_ADDLINE, start, end, PASSFLOAT(size1), PASSFLOAT(size2), PASSFLOAT(sizeParm), + PASSFLOAT(alpha1), PASSFLOAT(alpha2), PASSFLOAT(alphaParm), + sRGB, eRGB, PASSFLOAT(rgbParm), + killTime, shader, flags); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetDefaultState(int entityIndex, entityState_t *state ) +{ + return syscall( CG_GETDEFAULTSTATE, entityIndex, state ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); +} + +void trap_RW_SetTeam(int team, qboolean dead ) +{ + syscall( CG_RW_SETTEAM, team, dead ); +} + +void trap_ResetAutorun ( void ) +{ + syscall ( CG_RESETAUTORUN ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +int trap_PC_LoadGlobalDefines ( const char* filename ) +{ + return syscall ( CG_PC_LOAD_GLOBAL_DEFINES, filename ); +} + +void trap_PC_RemoveAllGlobalDefines ( void ) +{ + syscall ( CG_PC_REMOVE_ALL_GLOBAL_DEFINES ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { + return syscall( CG_R_INPVS, p1, p2 ); +} + +int trap_FX_RegisterEffect(const char *file) +{ + return syscall( CG_FX_REGISTER_EFFECT, file); +} + +void trap_FX_PlaySimpleEffect( const char *file, vec3_t org, int vol, int rad ) +{ + syscall( CG_FX_PLAY_SIMPLE_EFFECT, file, org, vol, rad ); +} + +void trap_FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT, file, org, fwd, vol, rad); +} + +void trap_FX_PlayEntityEffect( const char *file, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT, file, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlaySimpleEffectID( int id, vec3_t org, int vol, int rad ) +{ + syscall( CG_FX_PLAY_SIMPLE_EFFECT_ID, id, org, vol, rad ); +} + +void trap_FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT_ID, id, org, fwd, vol, rad ); +} + +void trap_FX_PlayEntityEffectID( int id, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT_ID, id, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlayBoltedEffectID(int id,CFxBoltInterface *obj, int vol, int rad ) +{ + syscall( CG_FX_PLAY_BOLTED_EFFECT_ID, id, obj, vol, rad); +} + +void trap_FX_AddScheduledEffects( void ) +{ + syscall( CG_FX_ADD_SCHEDULED_EFFECTS ); +} + +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ) +{ + syscall( CG_FX_DRAW_2D_EFFECTS, PASSFLOAT(screenXScale), PASSFLOAT(screenYScale) ); +} + +int trap_FX_InitSystem( refdef_t* refdef ) +{ + return syscall( CG_FX_INIT_SYSTEM, refdef ); +} + +qboolean trap_FX_FreeSystem( void ) +{ + return syscall( CG_FX_FREE_SYSTEM ); +} + +void trap_FX_Reset ( void ) +{ + syscall ( CG_FX_RESET ); +} + +void trap_FX_AdjustTime( int time ) +{ + syscall( CG_FX_ADJUST_TIME, time ); +} + +/* +Ghoul2 Insert Start +*/ +// CG Specific API calls +void trap_G2_ListModelSurfaces(void *ghlInfo) +{ + syscall( CG_G2_LISTSURFACES, ghlInfo); +} + +void trap_G2_ListModelBones(void *ghlInfo, int frame) +{ + syscall( CG_G2_LISTBONES, ghlInfo, frame); +} + +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList) +{ + syscall( CG_G2_SETMODELS, ghoul2, modelList, skinList); +} + +void trap_G2API_CollisionDetect ( + CollisionRecord_t *collRecMap, + void* ghoul2, + const vec3_t angles, + const vec3_t position, + int frameNumber, + int entNum, + const vec3_t rayStart, + const vec3_t rayEnd, + const vec3_t scale, + int traceFlags, + int useLod + ) +{ + syscall ( CG_G2_COLLISIONDETECT, collRecMap, ghoul2, angles, position, frameNumber, entNum, rayStart, rayEnd, scale, traceFlags, useLod ); +} + +int trap_G2API_AddBolt(void *ghoul2, const int modelIndex, const char *boneName) +{ + return (int) (syscall(CG_G2_ADDBOLT, ghoul2, modelIndex, boneName)); +} + +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo) +{ + syscall(CG_G2_SETBOLTON, ghoul2, modelIndex, boltInfo); +} + +qboolean trap_G2API_RemoveBolt(void *ghlInfo, const int modelIndex, const int index) +{ + return (qboolean)(syscall(CG_G2_REMOVEBOLT, ghlInfo, modelIndex, index)); +} + +qboolean trap_G2API_AttachG2Model(void *ghoul2From, int modelFrom, void *ghoul2To, int toBoltIndex, int toModel) +{ + return (qboolean)(syscall(CG_G2_ATTACHG2MODEL, ghoul2From, modelFrom, ghoul2To, toBoltIndex, toModel)); +} + +qboolean trap_G2API_DetachG2Model(void *ghoul2, int modelIndex) +{ + return (qboolean)(syscall(CG_G2_DETACHG2MODEL, ghoul2, modelIndex)); +} + +qboolean trap_G2_HaveWeGhoul2Models( void *ghoul2) +{ + return (qboolean)(syscall(CG_G2_HAVEWEGHOULMODELS, ghoul2)); +} + +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return (qboolean)(syscall(CG_G2_GETBOLT, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias) +{ + return syscall(CG_G2_INITGHOUL2MODEL, ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias); +} + +qboolean trap_G2API_GetAnimFileNameIndex ( TGhoul2 ghoul2, qhandle_t modelIndex, const char* filename ) +{ + return syscall(CG_G2_GETANIMFILENAMEINDEX, ghoul2, modelIndex, filename ); +} + +qhandle_t trap_G2API_RegisterSkin ( const char *skinName, int numPairs, const char *skinPairs) +{ + return syscall(CG_G2_REGISTERSKIN, skinName, numPairs, skinPairs ); +} + +qboolean trap_G2API_SetSkin ( TGhoul2 ghoul2, int modelIndex, qhandle_t customSkin) +{ + return syscall(CG_G2_SETSKIN, ghoul2, modelIndex, customSkin ); +} + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr) +{ + syscall(CG_G2_CLEANMODELS, ghoul2Ptr); +} + +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ + return (syscall(CG_G2_ANGLEOVERRIDE, ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime)); +} + +qboolean trap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ) +{ + return syscall(CG_G2_PLAYANIM, ghoul2, modelIndex, boneName, startFrame, endFrame, flags, PASSFLOAT(animSpeed), currentTime, PASSFLOAT(setFrame), blendTime); +} + +qboolean trap_G2API_GetBoneAnim( void *ghoul2, const int modelIndex, const char *boneName, const int currentTime, float* frame ) +{ + return syscall(CG_G2_GETANIM, ghoul2, modelIndex, boneName, currentTime, frame ); +} + +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const int modelIndex, const char *surfaceName, const int flags) +{ + return syscall(CG_G2_SETSURFACEONOFF, ghoul2, modelIndex, surfaceName, flags); +} + +qboolean trap_G2API_SetRootSurface(void **ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(CG_G2_SETROOTSURFACE, ghoul2, modelIndex, surfaceName); +} + +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int modelIndex, const int boltIndex) +{ + return syscall(CG_G2_SETNEWORIGIN, ghoul2, modelIndex, boltIndex); +} + +char *trap_G2API_GetGLAName(void *ghoul2, int modelIndex) +{ + return (char *)syscall(CG_G2_GETGLANAME, ghoul2, modelIndex); +} + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex) +{ + return syscall(CG_G2_COPYGHOUL2INSTANCE, g2From, g2To, modelIndex); +} + +int trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo) +{ + return syscall(CG_G2_COPYSPECIFICGHOUL2MODEL, g2From, modelFrom, g2To, modelTo); +} + +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To) +{ + syscall(CG_G2_DUPLICATEGHOUL2INSTANCE, g2From, g2To); +} + +qboolean trap_G2API_RemoveGhoul2Model(void **ghlInfo, int modelIndex) +{ + return syscall(CG_G2_REMOVEGHOUL2MODEL, ghlInfo, modelIndex); +} + +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore) +{ + syscall(CG_G2_ADDSKINGORE, ghlInfo, gore); +} + +void trap_G2API_ClearSkinGore ( void* ghlInfo ) +{ + syscall(CG_G2_CLEARSKINGORE, ghlInfo ); +} + +qboolean trap_G2API_SetGhoul2ModelFlags(void *ghlInfo,int flags) +{ + return(syscall(CG_G2_SETGHOUL2MODELFLAGS,ghlInfo,flags)); +} + +int trap_G2API_GetGhoul2ModelFlags(void *ghlInfo) +{ + return(syscall(CG_G2_GETGHOUL2MODELFLAGS,ghlInfo)); +} + +qboolean trap_G2API_SetGhoul2ModelFlagsByIndex(void *ghoul2,int index,int flags) +{ + return(syscall(CG_G2_SETGHOUL2MODELFLAGSBYINDEX,ghoul2,index,flags)); +} + +int trap_G2API_GetGhoul2ModelFlagsByIndex(void *ghoul2,int index) +{ + return(syscall(CG_G2_GETGHOUL2MODELFLAGSBYINDEX,ghoul2,index)); +} + +int trap_G2API_GetNumModels(TGhoul2 ghoul2) +{ + return syscall(CG_G2_GETNUMMODELS, ghoul2); +} + +int trap_G2API_FindBoltIndex(TGhoul2 ghoul2, const int modelIndex, const char *boneName) +{ + return syscall(CG_G2_FINDBOLTINDEX, ghoul2, modelIndex, boneName); +} + +int trap_G2API_GetBoltIndex(TGhoul2 ghoul2, const int modelIndex) +{ + return syscall(CG_G2_GETBOLTINDEX, ghoul2, modelIndex); +} + +void trap_MAT_Init(void) +{ + syscall(CG_MAT_CACHE); +} + +void trap_MAT_Reset(void) +{ + syscall(CG_MAT_RESET); +} + +sfxHandle_t trap_MAT_GetSound(char *key, int material) +{ + return (sfxHandle_t)syscall(CG_MAT_GET_SOUND, key, material); +} + +qhandle_t trap_MAT_GetDecal(char *key, int material) +{ + return (sfxHandle_t)syscall(CG_MAT_GET_DECAL, key, material); +} + +const float trap_MAT_GetDecalScale(char *key, int material) +{ + return (const float)syscall(CG_MAT_GET_DECAL_SCALE, key, material); +} + +qhandle_t trap_MAT_GetEffect(char *key, int material) +{ + return (sfxHandle_t)syscall(CG_MAT_GET_EFFECT, key, material); +} + +qhandle_t trap_MAT_GetDebris(char *key, int material) +{ + return (sfxHandle_t)syscall(CG_MAT_GET_DEBRIS, key, material); +} + +const float trap_MAT_GetDebrisScale(char *key, int material) +{ + return (const float)syscall(CG_MAT_GET_DEBRIS_SCALE, key, material); +} + +// CGenericParser2 (void *) routines +TGenericParser2 trap_GP_Parse(char **dataPtr, qboolean cleanFirst, qboolean writeable) +{ + return (TGenericParser2)syscall(GP_PARSE, dataPtr, cleanFirst, writeable); +} + +TGenericParser2 trap_GP_ParseFile(char *fileName, qboolean cleanFirst, qboolean writeable) +{ + return (TGenericParser2)syscall(GP_PARSE_FILE, fileName, cleanFirst, writeable); +} + +void trap_GP_Clean(TGenericParser2 GP2) +{ + syscall(GP_CLEAN, GP2); +} + +void trap_GP_Delete(TGenericParser2 *GP2) +{ + syscall(GP_DELETE, GP2); +} + +TGPGroup trap_GP_GetBaseParseGroup(TGenericParser2 GP2) +{ + return (TGPGroup)syscall(GP_GET_BASE_PARSE_GROUP, GP2); +} + + +// CGPGroup (void *) routines +qboolean trap_GPG_GetName(TGPGroup GPG, char *Value) +{ + return (qboolean)syscall(GPG_GET_NAME, GPG, Value); +} + +TGPGroup trap_GPG_GetNext(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_NEXT, GPG); +} + +TGPGroup trap_GPG_GetInOrderNext(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_INORDER_NEXT, GPG); +} + +TGPGroup trap_GPG_GetInOrderPrevious(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_INORDER_PREVIOUS, GPG); +} + +TGPGroup trap_GPG_GetPairs(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_PAIRS, GPG); +} + +TGPGroup trap_GPG_GetInOrderPairs(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_INORDER_PAIRS, GPG); +} + +TGPGroup trap_GPG_GetSubGroups(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_SUBGROUPS, GPG); +} + +TGPGroup trap_GPG_GetInOrderSubGroups(TGPGroup GPG) +{ + return (TGPGroup)syscall(GPG_GET_INORDER_SUBGROUPS, GPG); +} + +TGPValue trap_GPG_FindSubGroup(TGPGroup GPG, const char *name) +{ + return (TGPValue)syscall(GPG_FIND_SUBGROUP, GPG, name); +} + +TGPValue trap_GPG_FindPair(TGPGroup GPG, const char *key) +{ + return (TGPValue)syscall(GPG_FIND_PAIR, GPG, key); +} + +qboolean trap_GPG_FindPairValue(TGPGroup GPG, const char *key, const char *defaultVal, char *Value) +{ + return (qboolean)syscall(GPG_FIND_PAIRVALUE, GPG, key, defaultVal, Value); +} + + +// CGPValue (void *) routines +qboolean trap_GPV_GetName(TGPValue GPV, char *Value) +{ + return (qboolean)syscall(GPV_GET_NAME, GPV, Value); +} + +TGPValue trap_GPV_GetNext(TGPValue GPV) +{ + return (TGPValue)syscall(GPV_GET_NEXT, GPV); +} + +TGPValue trap_GPV_GetInOrderNext(TGPValue GPV) +{ + return (TGPValue)syscall(GPV_GET_INORDER_NEXT, GPV); +} + +TGPValue trap_GPV_GetInOrderPrevious(TGPValue GPV) +{ + return (TGPValue)syscall(GPV_GET_INORDER_PREVIOUS, GPV); +} + +qboolean trap_GPV_IsList(TGPValue GPV) +{ + return (qboolean)syscall(GPV_IS_LIST, GPV); +} + +qboolean trap_GPV_GetTopValue(TGPValue GPV, char *Value) +{ + return (qboolean)syscall(GPV_GET_TOP_VALUE, GPV, Value); +} + +TGPValue trap_GPV_GetList(TGPValue GPV) +{ + return (TGPValue)syscall(GPV_GET_LIST, GPV); +} + +void trap_CM_TM_Create(int terrainID) +{ + syscall(CG_CM_TM_CREATE, terrainID); +} + +void trap_CM_TM_AddBuilding(int x, int y, int side) +{ + syscall(CG_CM_TM_ADDBUILDING, x, y, side); +} + +void trap_CM_TM_AddSpot(int x, int y) +{ + syscall(CG_CM_TM_ADDSPOT, x, y); +} + +void trap_CM_TM_AddTarget(int x, int y, float radius) +{ + syscall(CG_CM_TM_ADDTARGET, x, y, PASSFLOAT(radius)); +} + +void trap_CM_TM_Upload(const vec3_t origin, const vec3_t angle) +{ + syscall(CG_CM_TM_UPLOAD, origin, angle); +} + +void trap_CM_TM_ConvertPosition(void) +{ + syscall(CG_CM_TM_CONVERT_POS); +} + +int trap_CM_RegisterTerrain(const char *config) +{ + return syscall(CG_CM_REGISTER_TERRAIN, config); +} + +void trap_RE_InitRendererTerrain( const char *info ) +{ + syscall(CG_RE_INIT_RENDERER_TERRAIN, info); +} + +void trap_CG_RegisterSharedMemory(char *memory) +{ + syscall(CG_SET_SHARED_BUFFER, memory); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) +{ + return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +void *trap_VM_LocalAlloc ( int size ) +{ + return (void *)syscall ( CG_VM_LOCALALLOC, size ); +} + +void *trap_VM_LocalAllocUnaligned ( int size ) +{ + return (void *)syscall ( CG_VM_LOCALALLOCUNALIGNED, size ); +} + +void *trap_VM_LocalTempAlloc( int size ) +{ + return (void *)syscall ( CG_VM_LOCALTEMPALLOC, size ); +} + +void trap_VM_LocalTempFree( int size ) +{ + syscall ( CG_VM_LOCALTEMPFREE, size ); +} + +const char *trap_VM_LocalStringAlloc ( const char *source ) +{ + return (const char *)syscall ( CG_VM_LOCALSTRINGALLOC, source ); +} + +void trap_UI_CloseAll ( void ) +{ + syscall ( CG_UI_CLOSEALL ); +} + +void trap_UI_SetActiveMenu ( int menu ) +{ + syscall ( CG_UI_SETACTIVEMENU, menu ); +} + diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c new file mode 100644 index 0000000..a884614 --- /dev/null +++ b/code/cgame/cg_view.c @@ -0,0 +1,1454 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) +{ + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) + { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) + { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + + if (! cg.testModelEntity.hModel ) + { + Com_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdef.viewangles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); +} + +void CG_TestModelNextFrame_f (void) +{ + cg.testModelEntity.frame++; + Com_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) +{ + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) + { + cg.testModelEntity.frame = 0; + } + + Com_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) +{ + cg.testModelEntity.skinNum++; + Com_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) +{ + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) + { + cg.testModelEntity.skinNum = 0; + } + Com_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) +{ + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) + { + Com_Printf ("Can't register model\n"); + return; + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) +{ + int size; + + size = 100; + + cg.refdef.width = cgs.glconfig.vidWidth*size/100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size/100; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; +} + +//============================================================================== + +//============================================================================== +//============================================================================== +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) +{ + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if ( timeDelta < STEP_TIME ) + { + cg.refdef.vieworg[2] -= cg.stepChange * (STEP_TIME - timeDelta) / STEP_TIME; + } +} + +#define CAMERA_DAMP_INTERVAL 50 + +static vec3_t cameramins = { -4, -4, -4 }; +static vec3_t cameramaxs = { 4, 4, 4 }; +vec3_t camerafwd, cameraup; + +vec3_t cameraFocusAngles, cameraFocusLoc; +vec3_t cameraIdealTarget, cameraIdealLoc; +vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0}; +vec3_t cameraOldLoc={0,0,0}, cameraNewLoc={0,0,0}; +int cameraLastFrame=0; + +/* +=============== +Notes on the camera viewpoint in and out... + +cg.refdef.vieworg +--at the start of the function holds the player actor's origin (center of player model). +--it is set to the final view location of the camera at the end of the camera code. +cg.refdef.viewangles +--at the start holds the client's view angles +--it is set to the final view angle of the camera at the end of the camera code. + +=============== +*/ + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewTarget(void) +{ + // Initialize IdealTarget + VectorCopy(cg.refdef.vieworg, cameraFocusLoc); + + // Add in the new viewheight + cameraFocusLoc[2] += cg.snap->ps.viewheight; + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. + VectorMA(cameraFocusLoc, 1, cameraup, cameraIdealTarget); +} + + + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewLocation(void) +{ + VectorMA(cameraIdealTarget, -(cg_thirdPersonRange.value), camerafwd, cameraIdealLoc); +} + + + +static void CG_ResetThirdPersonViewDamp(void) +{ + trace_t trace; + + // Cap the pitch within reasonable limits + if (cameraFocusAngles[PITCH] > 89.0) + { + cameraFocusAngles[PITCH] = 89.0; + } + else if (cameraFocusAngles[PITCH] < -89.0) + { + cameraFocusAngles[PITCH] = -89.0; + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + // Set the cameraIdealTarget + CG_CalcIdealThirdPersonViewTarget(); + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + // Now, we just set everything to the new positions. + VectorCopy(cameraIdealLoc, cameraCurLoc); + VectorCopy(cameraIdealTarget, cameraCurTarget); + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurLoc); + } + + cameraLastFrame = cg.time; +} + +// This is called every frame. +static void CG_UpdateThirdPersonTargetDamp(void) +{ + trace_t trace; + vec3_t targetdiff; + float dampfactor, dtime, ratio; + float damp = 1.0f; + + // Set the cameraIdealTarget + // Automatically get the ideal target, to avoid jittering. + CG_CalcIdealThirdPersonViewTarget(); + + if ( damp >=1.0) + { // No damping. + VectorCopy(cameraIdealTarget, cameraCurTarget); + } + else if ( damp >=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealTarget, cameraCurTarget, targetdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-damp; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg.time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, (int)dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealTarget, -ratio, targetdiff, cameraCurTarget); + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace to see if the new location is cool or not. + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +// This can be called every interval, at the user's discretion. +static void CG_UpdateThirdPersonCameraDamp(void) +{ + trace_t trace; + vec3_t locdiff; + float dampfactor, dtime, ratio; + float damp = 1.0f; + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + + // First thing we do is calculate the appropriate damping factor for the camera. + dampfactor=0.0; + if (damp != 0.0) + { + double pitch; + + // Note that the camera pitch has already been capped off to 89. + pitch = Q_fabs(cameraFocusAngles[PITCH]); + + // The higher the pitch, the larger the factor, so as you look up, it damps a lot less. + pitch /= 89.0; + dampfactor = (1.0-damp)*(pitch*pitch); + + dampfactor += damp; + } + + if (dampfactor>=1.0) + { // No damping. + VectorCopy(cameraIdealLoc, cameraCurLoc); + } + else if (dampfactor>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealLoc, cameraCurLoc, locdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-dampfactor; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg.time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, (int)dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealLoc, -ratio, locdiff, cameraCurLoc); + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction < 1.0) + { + VectorCopy( trace.endpos, cameraCurLoc ); + //FIXME: when the trace hits movers, it gets very very jaggy... ? + /* + //this doesn't actually help any + if ( trace.entityNum != ENTITYNUM_WORLD ) + { + centity_t *cent = &cg_entities[trace.entityNum]; + gentity_t *gent = &g_entities[trace.entityNum]; + if ( cent != NULL && gent != NULL ) + { + if ( cent->currentState.pos.trType == TR_LINEAR || cent->currentState.pos.trType == TR_LINEAR_STOP ) + { + vec3_t diff; + VectorSubtract( cent->lerpOrigin, gent->currentOrigin, diff ); + VectorAdd( cameraCurLoc, diff, cameraCurLoc ); + } + } + } + */ + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +/* +===============` +CG_OffsetThirdPersonView + +=============== +*/ +extern vmCvar_t cg_thirdPersonHorzOffset; +static void CG_OffsetThirdPersonView( void ) +{ + vec3_t diff; + + // Set camera viewing direction. + VectorCopy( cg.refdef.viewangles, cameraFocusAngles ); + + // if dead, look at killer + if ( 0 && cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + cameraFocusAngles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + cameraFocusAngles[PITCH] = 90; + } + else + { // Add in the third Person Angle. + cameraFocusAngles[YAW] += cg_thirdPersonYaw.value; + cameraFocusAngles[PITCH] += cg_thirdPersonPitch.value; + } + + // The next thing to do is to see if we need to calculate a new camera target location. + + // If we went back in time for some reason, or if we just started, reset the sample. + if (cameraLastFrame == 0 || cameraLastFrame > cg.time) + { + CG_ResetThirdPersonViewDamp(); + } + else + { + // Cap the pitch within reasonable limits + if (cameraFocusAngles[PITCH] > 89.0) + { + cameraFocusAngles[PITCH] = 89.0; + } + else if (cameraFocusAngles[PITCH] < -89.0) + { + cameraFocusAngles[PITCH] = -89.0; + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + // Move the target to the new location. + CG_UpdateThirdPersonTargetDamp(); + CG_UpdateThirdPersonCameraDamp(); + } + + // Now interestingly, the Quake method is to calculate a target focus point above the player, and point the camera at it. + // We won't do that for now. + + // We must now take the angle taken from the camera target and location. + VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + VectorNormalize(diff); + vectoangles(diff, cg.refdef.viewangles); + + // Temp: just move the camera to the side a bit + if ( cg_thirdPersonHorzOffset.value != 0.0f ) + { + AnglesToAxis( cg.refdef.viewangles, cg.refdef.viewaxis ); + VectorMA( cameraCurLoc, cg_thirdPersonHorzOffset.value, cg.refdef.viewaxis[1], cameraCurLoc ); + } + + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(cameraCurLoc, cg.refdef.vieworg); + + cameraLastFrame=cg.time; +} + + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*//* +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdef.viewangles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdef.viewangles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdef.viewangles[PITCH] *= 0.5; + + AngleVectors( cg.refdef.viewangles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + if (!cg_cameraMode.integer) { + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + } + + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdef.viewangles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdef.viewangles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if ( timeDelta < STEP_TIME ) { + cg.refdef.vieworg[2] -= cg.stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +}*/ + +/* +=============== +CG_OffsetFirstPersonView +=============== +*/ +static void CG_OffsetFirstPersonView( void ) +{ + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + vec3_t right; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdef.viewangles; + + // If dead, fall to the ground + if ( cg.snap->ps.pm_type == PM_DEAD ) + { + int atime; + int htime; + float f; + + atime = (cg.time - cg.deathTime); + if ( atime > 450) + { + atime = 450; + } + + htime = (cg.time - cg.deathTime); + if ( htime > 350) + { + htime = 350; + } + + f = (float)atime / 450.0f; + cg.refdef.viewangles[ROLL] += ((cg.deathTime&0x100)?-1:1) * 40 * f; + cg.refdef.viewangles[PITCH] -= ((cg.deathTime&0x010)?-1:1) * 20 * f; + cg.refdef.viewangles[YAW] -= ((cg.deathTime&0x001)?-1:1) * 40 * f; + + f = (float)htime / 400.0f; + cg.refdef.vieworg[2] += (f * cg.predictedPlayerState.viewheight * 0.90f); + + return; + } + + // add angles based on damage kick + if ( cg.damageTime ) + { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) + { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + + if ( cg.v_dmg_roll != 255 ) + { + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + else + { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) + { + angles[PITCH] += ratio * cg.v_dmg_pitch; + + if ( cg.v_dmg_roll != 255 ) + { + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if ((cg.predictedPlayerState.pm_flags & PMF_DUCKED) && (cg.predictedPlayerState.groundEntityNum!=ENTITYNUM_NONE)) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if ((cg.predictedPlayerState.pm_flags & PMF_DUCKED) && (cg.predictedPlayerState.groundEntityNum!=ENTITYNUM_NONE)) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < DUCK_TIME) { + cg.refdef.vieworg[2] -= cg.duckChange + * (DUCK_TIME - timeDelta) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; + if (bob > 6) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[2] += cg.landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[2] += cg.landChange * f; + } + + // add step offset + CG_StepOffset(); + + // add kick offset + + if( cg.predictedPlayerState.pm_flags & PMF_LEANING ) + { + float leanOffset; + + leanOffset = (float)(cg.predictedPlayerState.leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET; + angles[ROLL] += leanOffset / 4; + AngleVectors( cg.predictedPlayerState.viewangles, NULL, right, NULL ); + VectorMA( origin, leanOffset, right, origin ); + } + + // Make sure view doesnt invert on itself + angles[PITCH] = Com_Clampf ( -89, 89, angles[PITCH] ); +} + + +/* +==================== +CG_CalcFovFromX + +Calcs Y FOV from given X FOV +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +qboolean CG_CalcFOVFromX( float fov_x ) +{ + float x; +// float phase; +// float v; +// int contents; + float fov_y; + qboolean inwater; + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // there's a problem with this, it only takes the leafbrushes into account, not the entity brushes, + // so if you give slime/water etc properties to a func_door area brush in order to move the whole water + // level up/down this doesn't take into account the door position, so warps the view the whole time + // whether the water is up or not. Fortunately there's only one slime area in Trek that you can be under, + // so lose it... +#if 0 +/* + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } +*/ +#else + inwater = qfalse; +#endif + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + return (inwater); +} + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } + else + { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) + { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } + else + { + fov_x = cg_fov.value; + if ( fov_x < 1 ) + { + fov_x = 1; + } + else if ( fov_x > 160 ) + { + fov_x = 160; + } + } + + // If cheats arent enabled then 80 is the lowest and 100 is the highest + if ( !cg.cheats ) + { + if ( fov_x < 80 ) + { + fov_x = 80; + } + else if ( fov_x > 100 ) + { + fov_x = 100; + } + } + + if ( cg.predictedPlayerState.pm_flags & PMF_ZOOMED ) + { + zoomFov = (float)cg.predictedPlayerState.zoomFov; + + if (!cg.predictedPlayerState.pm_flags & PMF_ZOOM_LOCKED ) + { + zoomFov -= cg.frametime * 0.05f; + + if (zoomFov < MAX_ZOOM_FOV) + { + zoomFov = MAX_ZOOM_FOV; + } + else if (zoomFov > cg_fov.value) + { + zoomFov = cg_fov.value; + } + else + { // Still zooming + static zoomSoundTime = 0; + + if (zoomSoundTime < cg.time || zoomSoundTime > cg.time + 10000) + { + zoomSoundTime = cg.time + 300; + } + } + } + + fov_x = zoomFov; + } + else + { + f = ( cg.time - cg.predictedPlayerState.zoomTime ) / ZOOM_OUT_TIME; + if ( f > 1.0 ) + { + fov_x = fov_x; + } + else + { + fov_x = cg.predictedPlayerState.zoomFov + f * ( fov_x - cg.predictedPlayerState.zoomFov ); + } + } + } + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !(cg.predictedPlayerState.pm_flags&PMF_ZOOMED) ) + { + cg.zoomSensitivity = 1; + } + else + { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + +static void CG_DamageBlendBlob( void ) +{ + int t = 250; + int maxTime = DAMAGE_TIME; + vec3_t lineStart; + vec3_t lineEnd; + vec3_t rgb1; + vec3_t rgb2; + qboolean dmgIsToTheLeft; + qboolean dmgIsAbove; + qboolean unknownDir; + float alphaVal; + float cvarScale; + + if (cg_damageindicator.value <= 0) + { + return; + } + + t = cg.time - cg.damageTime; + if ( t <= 0 || t >= maxTime ) + { + return; + } + + // here we use cg.damageY as a value to indicate how much above you the damage is and + //cg.damageX as a value to indicate how much to your left the damage is + + // draw a "line" using the cgs.media.damageDirShader + dmgIsToTheLeft = (cg.damageX > 0); + dmgIsAbove = (cg.damageY < 0); + unknownDir = (cg.v_dmg_roll == 255); + alphaVal = ( 1.0 - ((float)t / maxTime) ); + cvarScale = 1.0f * cg_damageindicator.value; + + VectorSet(rgb1, 1, 1, 1); + VectorSet(rgb2, 1, 1, 1); + + // draw the left/right indicator if it's far enough from center + if ( (fabs(cg.damageX) > 25) || unknownDir) + { + VectorMA(cg.refdef.vieworg, 20, cg.refdef.viewaxis[0], lineStart); + VectorMA(lineStart, (dmgIsToTheLeft?6:-6), cg.refdef.viewaxis[1], lineStart); + VectorMA(lineStart, (dmgIsToTheLeft?2:-2)*cvarScale, cg.refdef.viewaxis[1], lineEnd); + + trap_FX_AddLine( lineStart, lineEnd, + 5.0f*cvarScale, 5.0f*cvarScale, 0.0f, + alphaVal, alphaVal, 0.0f, + rgb1, rgb2, 0.0f, + 1, cgs.media.damageDirShader, FX_DEPTH_HACK ); + } + if (unknownDir) + { + // dmg from unknown direction...display all four directions + VectorMA(cg.refdef.vieworg, 20, cg.refdef.viewaxis[0], lineStart); + VectorMA(lineStart, (dmgIsToTheLeft?-6:6), cg.refdef.viewaxis[1], lineStart); + VectorMA(lineStart, (dmgIsToTheLeft?-2:2)*cvarScale, cg.refdef.viewaxis[1], lineEnd); + + trap_FX_AddLine( lineStart, lineEnd, + 5.0f*cvarScale, 5.0f*cvarScale, 0.0f, + alphaVal, alphaVal, 0.0f, + rgb1, rgb2, 0.0f, + 1, cgs.media.damageDirShader, FX_DEPTH_HACK ); + } + + // draw the above/below indicator + if ( (fabs(cg.damageY) > 15) || unknownDir) + { + VectorMA(cg.refdef.vieworg, 20, cg.refdef.viewaxis[0], lineStart); + VectorMA(lineStart, (dmgIsAbove?6:-6), cg.refdef.viewaxis[2], lineStart); + VectorMA(lineStart, (dmgIsAbove?2:-2)*cvarScale, cg.refdef.viewaxis[2], lineEnd); + + trap_FX_AddLine( lineStart, lineEnd, + 5.0f*cvarScale, 5.0f*cvarScale, 0.0f, + alphaVal, alphaVal, 0.0f, + rgb1, rgb2, 0.0f, + 1, cgs.media.damageDirShader, FX_DEPTH_HACK ); + } + if (unknownDir) + { + VectorMA(cg.refdef.vieworg, 20, cg.refdef.viewaxis[0], lineStart); + VectorMA(lineStart, (dmgIsAbove?-6:6), cg.refdef.viewaxis[2], lineStart); + VectorMA(lineStart, (dmgIsAbove?-2:2)*cvarScale, cg.refdef.viewaxis[2], lineEnd); + + trap_FX_AddLine( lineStart, lineEnd, + 5.0f*cvarScale, 5.0f*cvarScale, 0.0f, + alphaVal, alphaVal, 0.0f, + rgb1, rgb2, 0.0f, + 1, cgs.media.damageDirShader, FX_DEPTH_HACK ); + } +} + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) +{ + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); + // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg.predictedPlayerState; +/* + if (cg.cameraMode) { + vec3_t origin, angles; + if (trap_getCameraInfo(cg.time, &origin, &angles)) { + VectorCopy(origin, cg.refdef.vieworg); + angles[ROLL] = 0; + VectorCopy(angles, cg.refdef.viewangles); + AnglesToAxis( cg.refdef.viewangles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } else { + cg.cameraMode = qfalse; + } + } +*/ + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) + { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdef.viewangles ); + AnglesToAxis( cg.refdef.viewangles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdef.viewangles ); + + if (cg_cameraOrbit.integer) + { + if (cg.time > cg.nextOrbitTime) + { + cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; + cg_thirdPersonYaw.value += cg_cameraOrbit.value; + } + } + + // add error decay + if ( cg_errorDecay.value > 0 ) + { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson && !(cg.snap->ps.pm_flags & PMF_ZOOMED) ) + { + // back away from character + CG_OffsetThirdPersonView(); + } + else + { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + CG_UpdateCameraShake ( cg.refdef.vieworg, cg.refdef.viewangles ); + + // position eye reletive to origin + AnglesToAxis( cg.refdef.viewangles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) { + if ( !sfx ) + return; + cg.soundBuffer[cg.soundBufferIn] = sfx; + cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; + if (cg.soundBufferIn == cg.soundBufferOut) { + cg.soundBufferOut++; + } +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) { + if ( cg.soundTime < cg.time ) { + if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { + trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); + cg.soundBuffer[cg.soundBufferOut] = 0; + cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; + cg.soundTime = cg.time + 750; + } + } +} + +//========================================================================= + +/* +================= +CG_UpdateRogerWilco + +Updates the roger wilco team you are on +================= +*/ +void CG_UpdateRogerWilco ( void ) +{ + qboolean dead = qfalse; + + if ( !rw_enabled.integer ) + { + return; + } + + if ( cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR ) + { + if ( cgs.gametypeData->respawnType == RT_NONE ) + { + if ( cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW) || cg.predictedPlayerState.pm_type == PM_DEAD ) + { + dead = qtrue; + } + } + } + + trap_RW_SetTeam(cgs.clientinfo[cg.clientNum].team, dead); +} + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars(); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg.infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + + trap_FX_AdjustTime( cg.time ); + + CG_UpdateRogerWilco ( ); + + CG_RunLightStyles(); + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(qfalse); + + // clear all the render lists + trap_R_ClearScene(); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_DrawInformation(); + return; + } + + // Handle last weapon selection + if ( cg.weaponSelect != cg.weaponOldSelect ) + { + cg.weaponLastSelect = cg.weaponOldSelect; + cg.weaponOldSelect = cg.weaponSelect; + } + + // let the client system know what our weapon and zoom settings are + if ( cg.weaponMenuUp ) + { + trap_SetUserCmdValue( (cg.weaponMenuSelect | WP_DELAYED_CHANGE_BIT), cg.zoomSensitivity ); + } + else + { + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + } + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // update the scores from the playerstate + if ( cgs.scores1 != cg.predictedPlayerState.persistant[PERS_RED_SCORE] ) + { + cgs.scores1 = cg.predictedPlayerState.persistant[PERS_RED_SCORE]; + trap_Cvar_Set ( "ui_info_redscore", va("%d", cgs.scores1 ) ); + } + + if ( cgs.scores2 != cg.predictedPlayerState.persistant[PERS_BLUE_SCORE] ) + { + cgs.scores2 = cg.predictedPlayerState.persistant[PERS_BLUE_SCORE]; + trap_Cvar_Set ( "ui_info_bluescore", va("%d", cgs.scores2 ) ); + } + + // decide on third person view + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || (cg.snap->ps.pm_flags & PMF_GHOST)) + { + cg.renderingThirdPerson = qfalse; + } + else if ( cg.snap->ps.pm_type == PM_DEAD ) + { + cg.renderingThirdPerson = cg_thirdPerson.integer; + } + else + { + if ( cg.snap->ps.pm_flags & PMF_ZOOMED ) + { + cg.renderingThirdPerson = qfalse; + } + else if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + cg.renderingThirdPerson = qtrue; + } + else + { + cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + } + } + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) { + CG_DamageBlendBlob(); + } + + // Load deferred models + if ( cg.deferredPlayerLoading > 10 ) + { + CG_LoadDeferredPlayers(); + } + + // build the render lists + if ( !cg.hyperspace ) + { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddLocalEntities(); + CG_DrawMiscEnts(); + } + + if ( cg.snap->ps.stats[STAT_HEALTH] > 0 ) + { + CG_AnimateViewWeapon(&cg.predictedPlayerState); + CG_AddViewWeapon( &cg.predictedPlayerState ); + } + + if ( !cg.hyperspace ) + { + trap_FX_AddScheduledEffects(); + } + + // add buffered sounds + CG_PlayBufferedSounds(); + + // play buffered voice chats + CG_PlayBufferedVoiceChats(); + + // finish up the rest of the refdef + if ( cg.testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + // update audio positions + if ( !cg.mMapChange ) + { + trap_AS_UpdateAmbientSet( CG_ConfigString( CS_AMBIENT_SOUNDSETS ), cg.refdef.vieworg ); // MUST be before trap_S_Respatialize()! -ste + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + } + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) + { + Com_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } +} + +/* +================= +CG_CameraShake + +Shakes the camera a bit (used in grenades) +================= +*/ +#define MAX_SHAKE_INTENSITY 2.0f +#define DEFAULT_EXPLOSION_RADIUS 400 + +void CG_CameraShake ( float* origin, float intensity, int radius, int time ) +{ + vec3_t dir; + float dist, intensityScale; + float realIntensity; + + VectorSubtract( cg.refdef.vieworg, origin, dir ); + dist = VectorNormalize( dir ); + + // Apparently the SoF2 camera shake function takes a time, but not a radius. + // when someone feels like fixing this, we can be a bit more flexible + radius = DEFAULT_EXPLOSION_RADIUS; + + //Use the dir to add kick to the explosion + if ( dist > radius ) + return; + + intensityScale = 1 - ( dist / (float) radius ); + realIntensity = intensity * intensityScale; + + if ( realIntensity > MAX_SHAKE_INTENSITY ) + realIntensity = MAX_SHAKE_INTENSITY; + + cg.shakeIntensity = realIntensity; + cg.shakeDuration = time; + cg.shakeStart = cg.time; +} + +/* +================= +CG_UpdateCameraShake + +Updates the camera shake +================= +*/ +void CG_UpdateCameraShake ( vec3_t origin, vec3_t angles ) +{ + vec3_t moveDir; + float intensity_scale; + float intensity; + int i; + + if ( cg.shakeDuration <= 0 ) + return; + + if ( cg.time > ( cg.shakeStart + cg.shakeDuration ) ) + { + cg.shakeIntensity = 0; + cg.shakeDuration = 0; + cg.shakeStart = 0; + return; + } + + //intensity_scale now also takes into account FOV with 90.0 as normal + intensity_scale = 1.0f - ( (float) ( cg.time - cg.shakeStart ) / (float) cg.shakeDuration ) * (((cg.refdef.fov_x+cg.refdef.fov_y)/2.0f)/90.0f); + intensity = cg.shakeIntensity * intensity_scale; + + for ( i = 0; i < 3; i++ ) + { + moveDir[i] = flrand(-intensity, intensity ); + } + + //FIXME: Lerp + + //Move the camera + VectorAdd( origin, moveDir, origin ); + + for ( i = 0; i < 3; i++ ) + { + moveDir[i] = flrand(-intensity, intensity ); + } + + //FIXME: Lerp + + //Move the angles + VectorAdd( angles, moveDir, angles ); +} diff --git a/code/cgame/cg_weaponinit.c b/code/cgame/cg_weaponinit.c new file mode 100644 index 0000000..40f2150 --- /dev/null +++ b/code/cgame/cg_weaponinit.c @@ -0,0 +1,474 @@ +// Copyright (C) 2001-2002 Raven Software, Inc. +// +// cg_weaponinit.c -- weapon initialization + +#include "cg_local.h" +#include "../game/bg_weapons.h" + +// FIMXE: This is defined in a C++ header file. +// We need to break it up or somethin so we don't have mutliple defs. +#define GHOUL2_NORENDER 2 + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) +{ + itemInfo_t *itemInfo; + gitem_t *item; + char simpleName[MAX_QPATH]; + + if ( itemNum < 0 || itemNum >= bg_numItems ) + { + Com_Error( ERR_FATAL, "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); + } + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) + { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + + // Check to see if its a ghoul model we loaded + if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm")) + { + trap_G2API_InitGhoul2Model(&itemInfo->g2Models[0], item->world_model[0], 0 , 0, 0, 0, 0); + itemInfo->radius[0] = 60; + + // Special case to make sure some sufaces are show that should be + switch ( item->giTag ) + { + case WP_AK74_ASSAULT_RIFLE: + trap_G2API_SetSurfaceOnOff( itemInfo->g2Models[0], 0, "bayonet_off", 0 ); + break; + + case WP_M4_ASSAULT_RIFLE: + trap_G2API_SetSurfaceOnOff( itemInfo->g2Models[0], 0, "m203_off", 0 ); + break; + } + } + + itemInfo->icon = trap_R_RegisterShaderNoMip( item->icon ); + + if (item->icon[0]) + { + Com_sprintf(simpleName, sizeof(simpleName), "%s_simple", item->icon); + itemInfo->mSimpleIcon = trap_R_RegisterShader(simpleName); + } + else + { + itemInfo->mSimpleIcon = 0; + } + + + if ( item->giType == IT_WEAPON ) + { + CG_RegisterWeapon( item->giTag ); + } +} + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon(int weaponNum) +{ + weaponInfo_t *weaponInfo; + weaponData_t *weaponDat; + TWeaponModel *weaponModel; + TOptionalWeapon *optionalPart; + gitem_t *item,*ammo; + TWeaponParseInfo *wPI; + int weaponIndex; // model index for view weapon models + int bufferIndex; // weapon + buffer + rhand [+ lhand ] + int rHandIndex; + int lHandIndex; + int boltonIndex; + int boltIndex; + int soundSet, i; + int *indexes; + + if(weaponNum==0) + { + return; + } + + boltonIndex = -1; + + assert(weaponNum < WP_NUM_WEAPONS); + + weaponInfo=&cg_weapons[weaponNum]; + if(weaponInfo->registered) + { + return; + } + + memset(weaponInfo,0,sizeof(*weaponInfo)); + + // Register the item visuals (icons etc). + weaponInfo->item = item = BG_FindWeaponItem ( weaponNum ); + + // Must be initial loading + if ( !weaponInfo->registered ) + { + CG_LoadingItem( item - bg_itemlist ); + } + + // Ok, successfully registered the entire weapon. + weaponInfo->registered=qtrue; + + weaponDat = &weaponData[weaponNum]; + + if(item->classname) + { + // Hmmm... this needs some work... some of it looks if now. + CG_RegisterItemVisuals( item - bg_itemlist ); + } + else + { + Com_Error( ERR_FATAL, "CG_RegisterWeapon: Couldn't find weapon item %i", weaponNum); + } + + // Register the weapon's world ammo model. + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) + { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) + { + break; + } + } + + if( ammo->classname && ammo->world_model[0] ) + { + trap_G2API_InitGhoul2Model(&weaponInfo->ammoG2Model,ammo->world_model[0],0,0,0,0,0); + } + + // Create the world model. + weaponIndex = trap_G2API_InitGhoul2Model(&weaponInfo->weaponG2Model,weaponDat->worldModel,0,0,0,0,0); + if(!trap_G2_HaveWeGhoul2Models(weaponInfo->weaponG2Model)) + { + Com_Printf("CG_RegisterWeapon: Unable to load weapon world model: %s\n", weaponDat->worldModel); + } + + // Register the weapon model... + indexes=weaponInfo->viewG2Indexes; + for(i=0;i<8;i++) + { + indexes[i]=-1; + } + + wPI=&weaponParseInfo[weaponNum]; + + // Create the view weapon model in slot 0. + indexes[0] = weaponIndex = trap_G2API_InitGhoul2Model(&weaponInfo->viewG2Model, + wPI->mWeaponModel.mModel,0,0,0,0,0); + + // Now build and assemble all the other bits. + if(trap_G2_HaveWeGhoul2Models(weaponInfo->viewG2Model)) + { + // Add a bolt to the weapon so buffer can be bolted on. + boltIndex=trap_G2API_AddBolt(weaponInfo->viewG2Model,weaponIndex,wPI->mWeaponModel.mBufferBoltToBone); + + // Every view weapon has a buffer, create this in slot 1. + indexes[1]=bufferIndex=trap_G2API_InitGhoul2Model(&weaponInfo->viewG2Model, + wPI->mWeaponModel.mBufferModel, + weaponIndex+1,0,0,0,0); + // Bolt the buffer to the weapon. + trap_G2API_AttachG2Model(weaponInfo->viewG2Model,bufferIndex, + weaponInfo->viewG2Model,boltIndex,weaponIndex); + + // Right hand?? + if(wPI->mWeaponModel.mRightHandsBoltToBone[0]) + { + // Add a right hand bolt to the buffer. + boltIndex=trap_G2API_AddBolt(weaponInfo->viewG2Model,bufferIndex, + wPI->mWeaponModel.mRightHandsBoltToBone); + + // Right hand.. create this now in slot 2. + indexes[2]=rHandIndex=trap_G2API_InitGhoul2Model(&weaponInfo->viewG2Model, + "models/weapons/rhand/rhand.glm", + weaponIndex+2,0,0,0,0); + + // Bolt the right hand to the buffer. + trap_G2API_AttachG2Model(weaponInfo->viewG2Model,rHandIndex,weaponInfo->viewG2Model, + boltIndex,bufferIndex); + } + + // Left hand?? + if(wPI->mWeaponModel.mLeftHandsBoltToBone[0]) + { + // Add a left hand bolt to the buffer. + boltIndex=trap_G2API_AddBolt(weaponInfo->viewG2Model,bufferIndex, + wPI->mWeaponModel.mLeftHandsBoltToBone); + + // Left hand.. create this now in slot 3. + indexes[3]=lHandIndex=trap_G2API_InitGhoul2Model(&weaponInfo->viewG2Model, + "models/weapons/lhand/lhand.glm", + weaponIndex+3,0,0,0,0); + + // Bolt the left hand to the buffer. + trap_G2API_AttachG2Model(weaponInfo->viewG2Model,lHandIndex,weaponInfo->viewG2Model, + boltIndex,bufferIndex); + } + + // Boltons like the M4's grenade launcher? + if(wPI->mWeaponModel.mBolton) + { + // Add a bolton bolt to the buffer. + boltIndex=trap_G2API_AddBolt(weaponInfo->viewG2Model,bufferIndex, + wPI->mWeaponModel.mBolton->mBoltToBone); + + // Bolton.. create this now in slot 4. + indexes[4]=boltonIndex=trap_G2API_InitGhoul2Model(&weaponInfo->viewG2Model, + wPI->mWeaponModel.mBolton->mModel, + weaponIndex+4,0,0,0,0); + // Bolt the bolton to the buffer. + if ( boltonIndex != -1 ) + { + trap_G2API_AttachG2Model(weaponInfo->viewG2Model,boltonIndex,weaponInfo->viewG2Model, + boltIndex,bufferIndex); + } + } + + // Turn of any weapon surfaces that are never seen. + weaponModel=&wPI->mWeaponModel; + i=0; + while(weaponModel->mRightSideSurfaces[i]) + { + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0], + weaponModel->mRightSideSurfaces[i],GHOUL2_NORENDER); + i++; + } + i=0; + while(weaponModel->mFrontSurfaces[i]) + { + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0], + weaponModel->mFrontSurfaces[i],GHOUL2_NORENDER); + i++; + } + if(weaponModel->mBolton && boltonIndex != -1 ) + { + i=0; + while(weaponModel->mBolton->mRightSide[i]) + { + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,boltonIndex, + weaponModel->mBolton->mRightSide[i],GHOUL2_NORENDER); + i++; + } + } + + // Turn off optional parts as we don't want em. + optionalPart=weaponModel->mOptionalList; + while(optionalPart) + { + // No real way of knowing what single player intended so we have to + // hard-code 'em. + if((!strcmp(optionalPart->mName,"lasersight"))|| + (!strcmp(optionalPart->mName,"silencer"))|| + (!strcmp(optionalPart->mName,"utl"))) + { + i=0; + while(optionalPart->mSurfaces[i]) + { + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0], + optionalPart->mSurfaces[i],GHOUL2_NORENDER); + i++; + } + } + optionalPart=optionalPart->mNext; + } + } + else + { + Com_Printf("CG_RegisterWeapon: Unable to load weapon view model: %s\n", wPI->mWeaponModel.mModel); + } + + weaponInfo->attack[ATTACK_NORMAL].missileTrailFunc = CG_ProjectileThink; + weaponInfo->attack[ATTACK_ALTERNATE].missileTrailFunc = CG_ProjectileThink; + + // Register weapon list menu icons. + if (weaponDat->menuImage[0]) + { + weaponInfo->weaponIcon = trap_R_RegisterShader( va( "gfx/menus/%s", weaponDat->menuImage) ); + } + + // Register the alternate weapon ammo icon for the hud + if (weaponDat->attack[ATTACK_ALTERNATE].icon[0] ) + { + weaponInfo->attack[ATTACK_ALTERNATE].ammoIcon = trap_R_RegisterShader( va( "gfx/menus/%s", weaponDat->attack[ATTACK_ALTERNATE].icon ) ); + } + + // Register attack stuff + for ( i = ATTACK_NORMAL; i < ATTACK_MAX; i ++ ) + { + attackData_t* attackData = &weaponDat->attack[i]; + attackInfo_t* attackInfo = &weaponInfo->attack[i]; + + // Primary muzzle-flash. + if (attackData->muzzleEffect[0]) + attackInfo->muzzleEffect = trap_FX_RegisterEffect(attackData->muzzleEffect); + if (attackData->muzzleEffectInWorld[0]) + attackInfo->muzzleEffectInWorld = trap_FX_RegisterEffect(attackData->muzzleEffectInWorld); + if (!attackInfo->muzzleEffectInWorld) + attackInfo->muzzleEffectInWorld = attackInfo->muzzleEffect; + + // Primary shell eject. + if (attackData->shellEject[0]) + attackInfo->shellEject = trap_FX_RegisterEffect(attackData->shellEject); + if (attackData->tracerEffect[0]) + attackInfo->tracerEffect = trap_FX_RegisterEffect(attackData->tracerEffect); + + // Primary explosion. + if (attackData->explosionEffect[0]) + attackInfo->explosionEffect = trap_FX_RegisterEffect(attackData->explosionEffect); + if (attackData->explosionSound[0]) + attackInfo->explosionSound = trap_S_RegisterSound(attackData->explosionSound); + if (attackData->missileG2Model[0]) + trap_G2API_InitGhoul2Model(&attackInfo->missileG2Model,attackData->missileG2Model,0,0,0,0,0); + + // Add the bolts for muzzle flash and shell eject onto the view model. + if (trap_G2_HaveWeGhoul2Models(weaponInfo->viewG2Model)) + { + // shell eject bone. + if ( attackData->ejectBone[0] ) + { + attackInfo->shellEjectBoltView = + trap_G2API_AddBolt(weaponInfo->viewG2Model, 1, attackData->ejectBone); + } + + // Muzzle flash bone. + attackInfo->muzzleFlashBoltView = -1; + if(attackData->muzzleEffect[0]) + { + if ( attackData->muzzleEffectBone[0] ) + { + attackInfo->muzzleFlashBoltView = + trap_G2API_AddBolt(weaponInfo->viewG2Model, 1, attackData->muzzleEffectBone ); + } + else if (wPI->mWeaponModel.mBufferMuzzle[0]) + { + attackInfo->muzzleFlashBoltView = + trap_G2API_AddBolt(weaponInfo->viewG2Model, 1, wPI->mWeaponModel.mBufferMuzzle); + } + } + } + + // Add the bolts for muzzle flash and shell eject onto the world model. + if (trap_G2_HaveWeGhoul2Models(weaponInfo->weaponG2Model)) + { + // shell eject bone. + if (attackData->ejectBone[0]) + { + attackInfo->shellEjectBoltWorld = + trap_G2API_AddBolt(weaponInfo->weaponG2Model, 0, attackData->ejectBone); + } + else + { + attackInfo->shellEjectBoltWorld = -1; + } + + // Muzzle flash bone. + attackInfo->muzzleFlashBoltWorld = -1; + if ( attackData->muzzleEffectBone[0] ) + { + attackInfo->muzzleFlashBoltWorld = + trap_G2API_AddBolt(weaponInfo->weaponG2Model, 0, attackData->muzzleEffectBone ); + } + else if (wPI->mWeaponModel.mBufferMuzzle[0]) + { + attackInfo->muzzleFlashBoltWorld = + trap_G2API_AddBolt(weaponInfo->weaponG2Model, 0, wPI->mWeaponModel.mBufferMuzzle); + } + } + } + + // Register sounds for weapon. + for (soundSet = 0; soundSet < MAX_WEAPON_SOUNDS; soundSet++) + { + if (Q_stricmp(wPI->mSoundNames[soundSet], "fire")==0) + { + for (i=0; imSounds[soundSet][i][0]) + { + weaponInfo->attack[ATTACK_NORMAL].flashSound[i] = trap_S_RegisterSound(wPI->mSounds[soundSet][i]); + } + } + } + else if (Q_stricmp(wPI->mSoundNames[soundSet], "altfire")==0) + { + for (i=0; imSounds[soundSet][i][0]) + weaponInfo->attack[ATTACK_ALTERNATE].flashSound[i] = trap_S_RegisterSound(wPI->mSounds[soundSet][i]); + } + } + else + { + // All other sounds. + for (i=0; imSounds[soundSet][i][0]) + weaponInfo->otherWeaponSounds[soundSet][i]=trap_S_RegisterSound(wPI->mSounds[soundSet][i]); + } + } + } + + // Special hard coded stuff + switch ( weaponNum ) + { + case WP_KNIFE: + { + attackInfo_t* attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE]; + attackInfo->missileSound = trap_S_RegisterSound ( "sound/weapons/knife/throw_loop" ); + break; + } + + case WP_RPG7_LAUNCHER: + { + attackInfo_t* attackInfo = &weaponInfo->attack[ATTACK_NORMAL]; + attackInfo->missileSound = trap_S_RegisterSound ( "sound/weapons/rpg7/flyby" ); + break; + } + } +} + +/* +================= +CG_ShutDownWeapons + +Clean out any g2 models +================= +*/ +void CG_ShutDownWeapons ( void ) +{ + int i; + attackType_t a; + + for (i=0; i < WP_NUM_WEAPONS; i++) + { + // free all ghoul2 models + trap_G2API_CleanGhoul2Models(&cg_weapons[i].weaponG2Model); + trap_G2API_CleanGhoul2Models(&cg_weapons[i].viewG2Model); + trap_G2API_CleanGhoul2Models(&cg_weapons[i].ammoG2Model); + + for ( a = ATTACK_NORMAL; a < ATTACK_MAX; a ++ ) + { + trap_G2API_CleanGhoul2Models(&cg_weapons[i].attack[a].missileG2Model); + } + } +} diff --git a/code/cgame/cg_weapons.c b/code/cgame/cg_weapons.c new file mode 100644 index 0000000..e351b46 --- /dev/null +++ b/code/cgame/cg_weapons.c @@ -0,0 +1,1783 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// cg_weapons.c -- events and effects dealing with weapons + +#include "cg_local.h" +#include "../game/bg_weapons.h" + +// set up the appropriate ghoul2 info to a refent +void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t *s1) +{ + ent->ghoul2 = s1->ghoul2; + VectorCopy( s1->modelScale, ent->modelScale); + ent->radius = s1->radius; + VectorCopy( s1->angles, ent->angles); +} + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzlePoint) +{ + weaponInfo_t *weaponInfo; + centity_t *cent; + refEntity_t muzzle; + int muzzleBolt; + attackInfo_t *attackInfo; + vec3_t rootAngles; + vec3_t lowerTorsoAngles; + vec3_t upperTorsoAngles; + vec3_t headAngles; + + cent = CG_GetEntity ( entityNum ); + if ( !cent->currentValid ) + { + return qfalse; + } + + BG_PlayerAngles( cent->lerpAngles, + NULL, + + rootAngles, + lowerTorsoAngles, + upperTorsoAngles, + headAngles, + + cent->currentState.leanOffset - LEAN_OFFSET, + + cent->pe.painTime, + cent->pe.painDirection, + cg.time, + + ¢->pe.torso, + ¢->pe.legs, + + cg.frametime, + cent->lerpVelocity, + (cent->currentState.eFlags & EF_DEAD), + cent->currentState.angles2[YAW], + cent->ghoul2 ); + + memset( &muzzle, 0, sizeof( muzzle ) ); + + if (!trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { // no player model and/or no gun! + return qfalse; + } + + weaponInfo = &cg_weapons[cent->currentState.weapon]; + + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE]; + } + else + { + attackInfo = &weaponInfo->attack[ATTACK_NORMAL]; + } + + muzzleBolt = attackInfo->muzzleFlashBoltWorld; + + if ( muzzleBolt == -1 ) + { + return qfalse; + } + + if ( G2_PositionEntityOnBolt(&muzzle, + cent->ghoul2, cent->pe.weaponModelSpot, muzzleBolt, + cent->lerpOrigin, rootAngles, cent->modelScale)) + { + VectorCopy(muzzle.origin, muzzlePoint); + return qtrue; + } + + return qfalse; +} + +/* +============= +CG_PlayerWeaponEffects + +Add any weapon effects like muzzle flash or ejecting brass +============= +*/ +void CG_PlayerWeaponEffects ( refEntity_t *parent, centity_t *cent, int team, vec3_t newAngles ) +{ + weapon_t weaponNum; + const weaponInfo_t *weaponInfo; + const weaponData_t *weaponDat; + const attackInfo_t *attackInfo; + const attackData_t *attackDat; + refEntity_t flash; + + cent->flashBoltInterface.isValid = qfalse; + cent->ejectBoltInterface.isValid = qfalse; + weaponNum = cent->currentState.weapon; + + weaponInfo = &cg_weapons[weaponNum]; + weaponDat = &weaponData[weaponNum]; + + // Dont do this for the player unless they are in 3rd person + if ( !cg.renderingThirdPerson ) + { + if ( cent->currentState.number == cg.predictedPlayerState.clientNum ) + { + return; + } + } + + // Make sure the player model is valid to attach to + if ( !trap_G2_HaveWeGhoul2Models(cent->ghoul2) ) + { + return; + } + + // The muzzle flash attach position needs to be updated for the duration of the effect + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME ) + { + return; + } + + // Alt fire or standard fire? + attackInfo = &weaponInfo->attack[cent->muzzleFlashAttack]; + attackDat = &weaponDat->attack[cent->muzzleFlashAttack]; + + // Position the flash entity on the muzzle flash bolt + memset (&flash, 0, sizeof(flash)); + if ( !G2_PositionEntityOnBolt( &flash, + cent->ghoul2, + cent->pe.weaponModelSpot, + attackInfo->muzzleFlashBoltWorld, + cent->lerpOrigin, + newAngles, + cent->modelScale ) ) + { + return; + } + + // Keep the muzzle flash moving + cent->flashBoltInterface.isValid = qtrue; + cent->flashBoltInterface.ghoul2 = cent->ghoul2; + cent->flashBoltInterface.modelNum = cent->pe.weaponModelSpot; + cent->flashBoltInterface.boltNum = attackInfo->muzzleFlashBoltWorld; + + VectorCopy ( flash.origin, cent->flashBoltInterface.origin ); + VectorCopy ( flash.axis[0], cent->flashBoltInterface.dir ); + VectorCopy ( flash.axis[0], cent->flashBoltInterface.forward ); + VectorCopy ( cent->modelScale, cent->flashBoltInterface.scale ); + + // No more flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) + { + return; + } + + // First frame of the flash? + if ( cg.time == cent->muzzleFlashTime ) + { + // Ejecting brass + if ( attackInfo->shellEject && cg_shellEjection.integer ) + { + refEntity_t eject; + + memset ( &eject, 0, sizeof(eject) ); + + // Now position the flash entity on the bolt itself. + if ( G2_PositionEntityOnBolt( &eject, + cent->ghoul2, + cent->pe.weaponModelSpot, + attackInfo->shellEjectBoltWorld, + cent->lerpOrigin, + newAngles, + cent->modelScale ) ) + { + vec3_t angles; + + eject.axis[0][0] = -eject.axis[0][0]; + eject.axis[0][1] = -eject.axis[0][1]; + eject.axis[0][2] = -eject.axis[0][2]; + + vectoangles ( eject.axis[0], angles ); + angles[YAW] += 90; + AnglesToAxis ( angles, eject.axis ); + + trap_FX_PlayEntityEffectID ( attackInfo->shellEject, eject.origin, eject.axis, -1, -1, -1, -1 ); + } + } + + // Muzzle Flash + if ( attackInfo->muzzleEffect ) + { + + if (attackDat->weaponFlags & UNLOCK_MUZZLEFLASH ) + { + // Muzzle flash not locked to barrel. + trap_FX_PlayEffectID(attackInfo->muzzleEffectInWorld, flash.origin, flash.axis[0], -1, -1 ); + } + else + { + trap_FX_PlayBoltedEffectID ( attackInfo->muzzleEffectInWorld, ¢->flashBoltInterface, -1, -1 ); + } + } + } + + // Add a dlight to the scene if it has a muzzle effect + if ( attackInfo->muzzleEffect ) + { + trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f ); + } +} + +// FIMXE: This is defined in a C++ header file. +// We need to break it up or somethin so we don't have mutliple defs. +#define GHOUL2_NORENDER 2 + +/* +============== +CG_StartViewAnimation + +Start a view animation for the given weapon and model index +============== +*/ +void CG_StartViewWeaponAnimation ( int weapon, int modelindex, int choice, TAnimInfoWeapon* aIW ) +{ + weaponInfo_t *weaponInfo; + int boneMode; + + weaponInfo = &cg_weapons[weapon]; + if ( weaponInfo->viewG2Indexes[modelindex] == -1 ) + { + return; + } + + boneMode = BONE_ANIM_OVERRIDE_DEFAULT; + +/* + if (cg_animBlend.integer) + { + boneMode |= BONE_ANIM_BLEND; + } +*/ + + trap_G2API_SetBoneAnim( weaponInfo->viewG2Model, + weaponInfo->viewG2Indexes[modelindex], + "model_root", + aIW->mStartFrame[choice], + aIW->mStartFrame[choice] + aIW->mNumFrames[choice], + boneMode, + 50.0f / (1000.0f / aIW->mFPS[choice]) * aIW->mSpeed, + cg.time, + aIW->mStartFrame[choice], + 150); + +} + +/* +============== +CG_AnimateViewWeapon + +Animation the view weapon +============== +*/ +void CG_AnimateViewWeapon ( playerState_t *ps ) +{ + int i; + int flags; + weaponInfo_t *weaponInfo; + + if( ps->pm_type == PM_SPECTATOR ) + { + return; + } + + for ( i = 0; i < 5; i ++ ) + { + if ( !cg.viewWeaponAnim[i] ) + { + continue; + } + + CG_StartViewWeaponAnimation ( ps->weapon, i, i==0?ps->weaponAnimIdChoice:0, cg.viewWeaponAnim[i] ); + cg.viewWeaponAnim[i] = NULL; + } + + // Need to turn on/off anything? + if(!cg.weaponHideModels) + { + return; + } + + // Ok... turn on/off models. + weaponInfo=&cg_weapons[ps->weapon]; + for ( i=0; i < 8; i++ ) + { + if( weaponInfo->viewG2Indexes[i] == -1 ) + { + continue; + } + + flags=trap_G2API_GetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i]); + if(cg.weaponHideModels&(1<viewG2Model,weaponInfo->viewG2Indexes[i], + flags|GHOUL2_NORENDER); + } + else + { + trap_G2API_SetGhoul2ModelFlagsByIndex(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[i], + flags&=~GHOUL2_NORENDER); + } + } +} + +/* +============== +CG_UpdateViewWeaponSurfaces + +Update the on/off bullet suraces of the inview weapon +============== +*/ +#define MAX_VIEWWEAPON_BULLETS 6 +void CG_UpdateViewWeaponSurfaces ( playerState_t* ps ) +{ + int numBullets; + int i; + const weaponInfo_t *weaponInfo; + + weaponInfo = &cg_weapons[ps->weapon]; + + numBullets = ps->clip[ATTACK_NORMAL][ps->weapon]; + + // When reloading just show it at zero, its easier that way + if ( ps->weaponstate == WEAPON_RELOADING || ps->weaponstate == WEAPON_RELOADING_ALT ) + { + numBullets = 0; + } + // Dont have more than the max bullets + else if (numBullets > MAX_VIEWWEAPON_BULLETS) + { + // toggle all of them + numBullets = MAX_VIEWWEAPON_BULLETS; + } + + // Make sure its registered + if ( !weaponInfo->registered ) + { + CG_RegisterWeapon ( ps->weapon ); + } + + // No model, nothing to do + if ( !weaponInfo->viewG2Model ) + { + return; + } + + switch ( ps->weapon ) + { + case WP_RPG7_LAUNCHER: + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockethead",numBullets?0:G2SURFACEFLAG_OFF); + trap_G2API_SetSurfaceOnOff(weaponInfo->viewG2Model,weaponInfo->viewG2Indexes[0],"rockettail",numBullets?0:G2SURFACEFLAG_OFF); + break; + + case WP_M1911A1_PISTOL: + case WP_USSOCOM_PISTOL: + { + int forward; + int backward; + + if ( numBullets == 0 ) + { + forward = G2SURFACEFLAG_OFF; + backward = 0; + } + else + { + forward = 0; + backward = G2SURFACEFLAG_OFF; + } + + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide", forward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef", forward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel", forward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider", forward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb", forward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slide_off", backward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidef_off", backward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slidel_off", backward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slider_off", backward ); + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], "slideb_off", backward ); + + break; + } + + case WP_M60_MACHINEGUN: + // Turn on whats available + for ( i = 0; i < numBullets; i++ ) + { + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), 0 ); + } + + // Turn the rest off + for (; i < MAX_VIEWWEAPON_BULLETS; i++) + { + trap_G2API_SetSurfaceOnOff( weaponInfo->viewG2Model, weaponInfo->viewG2Indexes[0], va("bullet%d", i + 1), G2SURFACEFLAG_OFF ); + } + break; + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon(playerState_t *ps) +{ + refEntity_t gun; + refEntity_t flash; + centity_t *cent; + float fovOffset; + const weaponInfo_t *weaponInfo; + const weaponData_t *weaponDat; + const attackInfo_t *attackInfo; + const attackData_t *attackDat; + vec3_t angles; + int delta; + vec3_t forward; + vec3_t vangles; + int speed; + + cg.flashBoltInterface.isValid = qfalse; + + if(ps->pm_type == PM_SPECTATOR || ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + return; + } + + if(ps->pm_type == PM_INTERMISSION) + { + return; + } + + // No gun if in third person view or a camera is active + //if ( cg.renderingThirdPerson || cg.cameraMode) { + if(cg.renderingThirdPerson) + { + return; + } + + cent = &cg_entities[ps->clientNum]; + + // allow the gun to be completely removed + if( !cg_drawGun.integer || (ps->pm_flags & PMF_ZOOMED) ) + { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 90 ) + { + fovOffset = -0.2 * ( cg_fov.integer - 90 ); + } + else + { + fovOffset = 0; + } + + CG_RegisterWeapon(ps->weapon); + weaponInfo = &cg_weapons[ps->weapon]; + weaponDat = &weaponData[ps->weapon]; + + // Add the weapon and hands models. + memset(&gun,0,sizeof(gun)); + gun.ghoul2 = weaponInfo->viewG2Model; + if(!trap_G2_HaveWeGhoul2Models(gun.ghoul2)) + { + // No weapon to draw! + return; + } + + VectorCopy ( cg.refdef.vieworg, gun.origin ); + + // Add movement bobbing + gun.origin[2] += cg.xyspeed * cg.bobfracsin * 0.0015f; + + // Add landing offset + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) + { + gun.origin[2] += cg.landChange * 0.25f * delta / LAND_DEFLECT_TIME; + } + else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) + { + gun.origin[2] += cg.landChange * 0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + + // Add movement offset + speed = sqrt( cg.predictedPlayerState.velocity[0] * cg.predictedPlayerState.velocity[0] + + cg.predictedPlayerState.velocity[1] * cg.predictedPlayerState.velocity[1] + + cg.predictedPlayerState.velocity[2] * cg.predictedPlayerState.velocity[2]); + + vectoangles ( cg.predictedPlayerState.velocity, vangles ); + vangles[1] += (360 - cg.predictedPlayerState.viewangles[1]); + vangles[2] += (360 - cg.predictedPlayerState.viewangles[2]); + AngleVectors ( vangles, forward, NULL, NULL); + + VectorScale ( forward, speed, forward ); + + VectorMA( gun.origin, forward[1] * 0.003f, cg.refdef.viewaxis[1], gun.origin ); + + // Set model scale + VectorSet ( gun.modelScale,weaponParseInfo[ps->weapon].mForeshorten,1.0f,1.0f ); + + vectoangles(cg.refdef.viewaxis[0],angles); + AnglesToAxis(angles,gun.axis); + CG_ScaleModelAxis(&gun); + + VectorCopy(gun.origin,gun.oldorigin); + gun.renderfx=RF_DEPTHHACK|RF_FIRST_PERSON|RF_MINLIGHT|RF_NO_FOG; + gun.radius=64; + + trap_R_AddRefEntityToScene(&gun); + + // The muzzle flash attach position needs to be updated for the duration of the effect + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_EFFECT_TIME ) + { + return; + } + + // Grab the proper attack info + if(cent->muzzleFlashAttack ) + { + attackInfo = &weaponInfo->attack[ATTACK_ALTERNATE]; + attackDat = &weaponDat->attack[ATTACK_ALTERNATE]; + } + else + { + attackInfo = &weaponInfo->attack[ATTACK_NORMAL]; + attackDat = &weaponDat->attack[ATTACK_NORMAL]; + } + + // Update the muzzle flashes origins + memset(&flash,0,sizeof(flash)); + VectorCopy ( gun.modelScale, flash.modelScale ); + if(!G2_PositionEntityOnBolt( &flash, + gun.ghoul2, + 1, + attackInfo->muzzleFlashBoltView, + gun.origin, + angles, + flash.modelScale ) ) + { + return; + } + + // Keep the muzzle flash origins updated + cg.flashBoltInterface.isValid = qtrue; + cg.flashBoltInterface.ghoul2 = gun.ghoul2; + cg.flashBoltInterface.modelNum = 1; + cg.flashBoltInterface.boltNum = attackInfo->muzzleFlashBoltView; + + VectorCopy(flash.axis[0],cg.flashBoltInterface.dir ); + VectorCopy(flash.axis[0],cg.flashBoltInterface.forward ); + VectorCopy(flash.origin,cg.flashBoltInterface.origin); + VectorCopy(flash.modelScale,cg.flashBoltInterface.scale); + + // If no muzzle flash to handle then there is nothing left to do + if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ) + { + return; + } + + // First frame for the muzzle flash is when the effects are added + if ( cg.time == cent->muzzleFlashTime ) + { + // Add the muzzle flash + if( attackInfo->muzzleEffect ) + { + // Handle locked and unlocked muzzle flashes + if(attackDat->weaponFlags & UNLOCK_MUZZLEFLASH ) + { + trap_FX_PlayEffectID(attackInfo->muzzleEffect,flash.origin,flash.axis[0], -1, -1 ); + } + else + { + trap_FX_PlayBoltedEffectID(attackInfo->muzzleEffect,&cg.flashBoltInterface, -1, -1 ); + } + } + + // Add shell ejection + if ( attackInfo->shellEject && cg_shellEjection.integer ) + { + refEntity_t shellEject; + + // Handle muzzle flashes + memset(&shellEject,0,sizeof(shellEject)); + + if(G2_PositionEntityOnBolt( &shellEject, + weaponInfo->viewG2Model, + 1, + attackInfo->shellEjectBoltView, + cg.refdef.vieworg, + cg.refdef.viewangles, + gun.modelScale ) ) + { + // Flip the forward axis so it goes backwards rather than forwards + shellEject.axis[0][0] = -shellEject.axis[0][0]; + shellEject.axis[0][1] = -shellEject.axis[0][1]; + shellEject.axis[0][2] = -shellEject.axis[0][2]; + + // Play the entity with a full axis + trap_FX_PlayEntityEffectID(attackInfo->shellEject,shellEject.origin,shellEject.axis, -1, -1, -1, -1 ); + } + } + + // Add tracers + if ( attackInfo->tracerEffect && !(attackDat->projectileLifetime & PROJECTILE_FIRE)) + { + // Lower the amount of tracers + if ( rand()%100 < (cg_tracerChance.value * 100.0f) ) + { + vec3_t origin; + VectorMA ( flash.origin, 500, gun.axis[0], origin ); + trap_FX_PlayEffectID ( attackInfo->tracerEffect, origin, gun.axis[0], -1, -1 ); + } + } + } + + // Add a dlight when there is a muzzle effect + if ( attackInfo->muzzleEffect ) + { + trap_R_AddLightToScene( flash.origin, 200, 0.6f, 0.4f, 0.2f ); + } +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=============== +CG_WeaponSelectable +=============== +*/ +qboolean CG_WeaponSelectable( int i, qboolean allowEmpty ) +{ + int ammo; + + if ( ! (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) + { + return qfalse; + } + + if ( allowEmpty && (cg.predictedPlayerState.pm_flags & PMF_LIMITED_INVENTORY) ) + { + return qtrue; + } + + if ( BG_WeaponHasAlternateAmmo ( i ) ) + { + if ( cg.predictedPlayerState.ammo[ weaponData[i].attack[ATTACK_ALTERNATE].ammoIndex ] || + cg.predictedPlayerState.clip[ATTACK_ALTERNATE][ i ] ) + { + return qtrue; + } + } + + // Start with normal ammo + ammo = 0; + ammo += cg.predictedPlayerState.clip[ATTACK_NORMAL][i]; + ammo += cg.predictedPlayerState.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex]; + + if ( !ammo ) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_NextWeapon + +selects the next weapon in the players inventory +=============== +*/ +void CG_NextWeapon ( qboolean allowEmpty, int exclude ) +{ + int i; + int original; + int selected; + + if ( !cg.snap ) + { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + return; + } + + if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING || + cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) + { + return; + } + + // When the weapon select menu is up the next and prev move through it + if ( cg.weaponMenuUp ) + { + selected = cg.weaponMenuSelect; + } + else + { + selected = cg.weaponSelect; + } + + original = selected; + + for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS; i++ ) + { + selected++; + if ( selected == WP_NUM_WEAPONS ) + { + selected = WP_NONE + 1; + } + if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) ) + { + break; + } + } + + if ( i == WP_NUM_WEAPONS ) + { + selected = original; + } + + // When the weapon select menu is up the next and prev move through it + if ( cg.weaponMenuUp ) + { + cg.weaponMenuSelect = selected; + } + else + { + cg.weaponSelect = selected; + } +} + +/* +=============== +CG_PrevWeapon + +Selects the previous weapon in the players inventory +=============== +*/ +void CG_PrevWeapon ( qboolean allowEmpty, int exclude ) +{ + int i; + int original; + int selected; + + if ( !cg.snap ) + { + return; + } + + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) + { + return; + } + + if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING || + cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) + { + return; + } + + // When the weapon select menu is up the next and prev move through it + if ( cg.weaponMenuUp ) + { + selected = cg.weaponMenuSelect; + } + else + { + selected = cg.weaponSelect; + } + + original = selected; + + for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS ; i++ ) + { + selected--; + if ( selected == WP_NONE ) + { + selected = WP_NUM_WEAPONS-1; + } + if ( selected != exclude && CG_WeaponSelectable( selected, allowEmpty ) ) + { + break; + } + } + + if ( i == WP_NUM_WEAPONS ) + { + selected = original; + } + + // When the weapon select menu is up the next and prev move through it + if ( cg.weaponMenuUp ) + { + cg.weaponMenuSelect = selected; + } + else + { + cg.weaponSelect = selected; + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) +{ + int num; + int category; + int last; + + if ( !cg.snap ) + { + return; + } + + if ( (cg.snap->ps.pm_flags & PMF_FOLLOW) ) + { + return; + } + + if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING || + cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) + { + return; + } + + category = atoi( CG_Argv( 1 ) ); + + if (category < 0 || category > CAT_MAX) + { + return; + } + + if ( !cg_weaponMenuFast.integer ) + { + if ( !cg.weaponMenuUp ) + { + cg.weaponMenuSelect = cg.predictedPlayerState.weapon; + cg.weaponMenuUp = qtrue; // show weapon menu + } + } + else + { + cg.weaponMenuSelect = cg.predictedPlayerState.weapon; + } + + last = -1; + if (category == weaponData[cg.weaponMenuSelect].category) + { + last = cg.weaponMenuSelect; + } + + // Find next weapon in currently active category. + for (num = WP_KNIFE; num < WP_NUM_WEAPONS; num++) + { + if(num>last && category==weaponData[num].category && (cg.predictedPlayerState.stats[STAT_WEAPONS]&(1<ps.stats[STAT_WEAPONS]&(1<currentState; + + // Dont bother if antilag is turned off or its a projectile weapon + if ( cg_antiLag.integer < 1 || !cg_impactPrediction.integer || (weaponData[ent->weapon].attack[attack].weaponFlags & PROJECTILE_FIRE)) + { + return; + } + + // Calculate the muzzle point + VectorCopy( cg.predictedPlayerState.origin, start ); + start[2] += cg.predictedPlayerState.viewheight; + + CG_UpdateViewWeaponSurfaces ( &cg.predictedPlayerState ); + + detailed = qtrue; + pellets = (weaponData[ent->weapon].attack[attack].pellets / 2) + 1; + pellets = weaponData[ent->weapon].attack[attack].pellets; + + // Handle leaning + VectorCopy(cg.predictedPlayerState.viewangles, fireAngs); + if ( cg.predictedPlayerState.pm_flags & PMF_LEANING ) + { + vec3_t right; + float leanOffset; + + leanOffset = (float)(cg.predictedPlayerState.leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET; + AngleVectors( fireAngs, NULL, right, NULL ); + VectorMA( start, leanOffset, right, start ); + } + + seed = cg.predictedPlayerState.stats[STAT_SEED]; + + // Current inaccuracy + inaccuracy = (float)cg.predictedPlayerState.inaccuracy / 1000.0f; + if ( detailed ) + { + if ( cg.predictedPlayerState.pm_flags & PMF_DUCKED ) + { + inaccuracy *= DUCK_ACCURACY_MODIFIER; + } + else if ( cg.predictedPlayerState.pm_flags & PMF_JUMPING ) + { + inaccuracy *= JUMP_ACCURACY_MODIFIER; + } + } + + for ( i = 0; i < pellets; i ++ ) + { + BG_CalculateBulletEndpoint ( start, fireAngs, inaccuracy, weaponData[ent->weapon].attack[attack].rV.range, end, &seed ); + + // See if it hit a wall + CG_Trace ( &tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT&~CONTENTS_BODY ); + + if ( tr.fraction >= 0.0f && tr.fraction <= 1.0f ) + { + VectorCopy ( tr.endpos, end ); + } + + CG_PlayerTrace ( &tr, start, end, cent->currentState.number ); + + if ( tr.entityNum >= cgs.maxclients && tr.entityNum != ENTITYNUM_NONE ) + { + CG_Bullet( tr.endpos, ent->number, ent->weapon, tr.plane.normal, ENTITYNUM_WORLD, + tr.surfaceFlags & MATERIAL_MASK, + attack ); + } +#ifdef _SOF2_FLESHIMPACTPREDICTION + else if ( tr.entityNum < cgs.maxclients ) + { + qboolean blood = qtrue; + vec3_t dir; + + if ( cg_impactPrediction.integer < 2 ) + { + return; + } + // If invulerable then no blood + if ( cg_entities[tr.entityNum].currentState.eFlags & EF_INVULNERABLE ) + { + blood = qfalse; + } + // If on the same team in friendly fire then no blood + else if ( cgs.gametypeData->teams && !cgs.friendlyFire && cgs.clientinfo[tr.entityNum].team == cgs.clientinfo[cg.snap->ps.clientNum].team ) + { + blood = qfalse; + } + + VectorSubtract ( end, start, dir ); + VectorNormalize ( dir ); + + // If no blood then just make a little white puff + if ( !blood || cg_lockBlood.integer ) + { + CG_Bullet( tr.endpos, ent->number, ent->weapon, dir, ENTITYNUM_WORLD, 0, attack ); + } + else + { + CG_PredictedProcGore ( ent->weapon, attack, start, end, &cg_entities[tr.entityNum] ); + + CG_MissileHitPlayer( ent->weapon, tr.endpos, dir, tr.entityNum, qfalse ); + } + } +#endif + } +} + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent, attackType_t attack ) +{ + entityState_t *ent; + int c; + weaponInfo_t *weaponInfo; + attackInfo_t *attackInfo; + + ent = ¢->currentState; + + if ( ent->weapon == WP_NONE ) + { + return; + } + + if ( ent->weapon >= WP_NUM_WEAPONS ) + { + Com_Error( ERR_FATAL, "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + + weaponInfo = &cg_weapons[ ent->weapon ]; + attackInfo = &weaponInfo->attack[attack]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + cent->muzzleFlashAttack = attack; + + // play a sound + for ( c = 0 ; c < 4 ; c++ ) + { + if ( !attackInfo->flashSound[c] ) + { + break; + } + } + + if ( c > 0 ) + { + c = rand() % c; + if ( attackInfo->flashSound[c] ) + { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, attackInfo->flashSound[c], -1, 2000 ); + } + } + + // Handle dissapearing bullets if this is the main guy firing + if ( cent->currentState.number == cg.snap->ps.clientNum ) + { + CG_PredictedBullet ( cent, attack ); + } +} + +/* +================ +CG_ProjectileThink + +Create trail fx for projectiles +================ +*/ +void CG_ProjectileThink( centity_t *cent, int weaponNum ) +{ + const weaponInfo_t *weaponInfo; + const weaponData_t *weaponDat; + vec3_t forward; + int trailEffectId; + + weaponInfo = &cg_weapons[weaponNum]; + weaponDat = &weaponData[weaponNum]; + + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + trailEffectId = weaponInfo->attack[ATTACK_ALTERNATE].tracerEffect; + } + else + { + trailEffectId = weaponInfo->attack[ATTACK_NORMAL].tracerEffect; + } + + // use tracer effect for projectile trails + if (trailEffectId) + { + // only calc normalize if there IS an effect + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID(trailEffectId, cent->lerpOrigin, forward, -1, -1 ); + } +} + +/* +================= +CG_FlashBang + +Blinds a player and makes them loose sound for a bit +================= +*/ + +#define MAX_FLASHBANG_AFFECT_DISTANCE 1750 +#define MAX_FLASHBANG_DISTANCE 3000 +#define MAX_FLASHBANG_TIME 11000 + +void CG_FlashBang ( vec3_t origin, vec3_t dir ) +{ + trace_t trace; + vec3_t start; + vec3_t end; + vec3_t delta; + float distance; + vec3_t fromangles; + int angle; + int time; + + // Dont flash grenade dead people + if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL ) + { + return; + } + + VectorCopy ( origin, start ); + start[2] += cg.snap->ps.viewheight; + + VectorCopy ( cg.refdef.vieworg, end ); + end[2] += cg.snap->ps.viewheight; + + VectorSubtract( cg.refdef.vieworg, origin, delta ); + + // distance to the flash bang + distance = VectorLength( delta ); + + // Make sure its not too far away + if(distance > MAX_FLASHBANG_DISTANCE ) + { + return; + } + + // Can the player see the flashbang? + CG_Trace ( &trace, start, NULL, NULL, end, ENTITYNUM_NONE, MASK_SHOT ); + if ( trace.contents & (CONTENTS_SOLID|CONTENTS_TERRAIN) ) + { + return; + } + + VectorNormalize ( delta ); + vectoangles ( delta, fromangles ); + + angle = cg.snap->ps.viewangles[1] - fromangles[1]; + angle += 180; + + if(angle < 0) + { + angle += 360; + } + + // add distance if not facing the grenade.. + if(!(angle > 300 || angle < 60 )) + { + angle = 120 - abs(angle-180); + distance += (MAX_FLASHBANG_AFFECT_DISTANCE * angle / 240); + } + + if(distance > MAX_FLASHBANG_AFFECT_DISTANCE - 50 ) + { + distance = MAX_FLASHBANG_AFFECT_DISTANCE - 50; + } + + distance = MAX_FLASHBANG_AFFECT_DISTANCE - distance; + time = MAX_FLASHBANG_TIME; + + cg.flashbangTime = cg.time; + cg.flashbangFadeTime = (MAX_FLASHBANG_TIME * distance / MAX_FLASHBANG_AFFECT_DISTANCE); + cg.flashbangAlpha = 1.0f * distance / MAX_FLASHBANG_AFFECT_DISTANCE; + + // If NVG or thermals were on then blast them a bit more + if ( (cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON) ) + { + cg.flashbangFadeTime += (cg.flashbangFadeTime / 4); + } +} + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall ( + int weapon, + vec3_t origin, + vec3_t dir, + int material, + attackType_t attack + ) +{ + qhandle_t impactEffect; + int ammoIndex; + + // Flash grenades are handled very differnetly + if ( weapon == WP_M84_GRENADE ) + { + CG_FlashBang ( origin, dir ); + } + + // Melee is handled specialol, the standard bullet routines are used + // to handle hit detection and what not, but the effects are handled + // separately. + if ( weaponData[weapon].attack[attack].melee ) + { + impactEffect = trap_MAT_GetEffect( (char*)weaponData[weapon].attack[attack].melee, material&MATERIAL_MASK); + if (impactEffect) + { + trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 ); + } + + return; + } + + ammoIndex = weaponData[weapon].attack[attack].ammoIndex; + + switch( ammoIndex ) + { + default: + case AMMO_KNIFE: + case AMMO_045: + case AMMO_556: + case AMMO_9 : + case AMMO_12 : + case AMMO_762: + impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK); + if (impactEffect) + { + trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 ); + } + break; + + case AMMO_40: + case AMMO_M15: + case AMMO_M67: + case AMMO_M84: + case AMMO_F1: + case AMMO_RPG7: + case AMMO_L2A2: + case AMMO_MDN11: + case AMMO_SMOHG92: + case AMMO_ANM14: + + impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, material&MATERIAL_MASK); + if (impactEffect) + { + trap_FX_PlayEffectID( impactEffect, origin, dir, -1, -1 ); + } + + // grenade! + trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000 ); + + break; + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer ( + int weapon, + vec3_t origin, + vec3_t dir, + int entityNum, + attackType_t attack + ) +{ + qhandle_t impactEffect = 0; + int ammoIndex; + + ammoIndex = weaponData[weapon].attack[attack].ammoIndex; + + switch( ammoIndex ) + { + default: + case AMMO_KNIFE: + case AMMO_045: + case AMMO_556: + case AMMO_9 : + case AMMO_12 : + case AMMO_762: + + if ( entityNum == cg.clientNum ) + { // The person hit is the local player. + if (!cg_lockBlood.integer) + { // Only play the exit wound effect + trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1 ); + } + } + else if (cg_lockBlood.integer) + { // Only play the generic hit effect. + trap_FX_PlayEffectID( trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE), + origin, dir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.media.mBloodSmall, origin, dir, -1, -1 ); + trap_FX_PlayEffectID( cgs.media.playerFleshImpactEffect, origin, dir, -1, -1 ); // Exit wound too. + } + + break; + + case AMMO_RPG7: + case AMMO_40: + case AMMO_M15: + case AMMO_M67: + case AMMO_M84: + case AMMO_F1: + case AMMO_L2A2: + case AMMO_MDN11: + case AMMO_SMOHG92: + case AMMO_ANM14: + if (cg_lockBlood.integer) + { // Play the generic hit effect. + impactEffect = trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_NONE); + } + else + { + impactEffect = cgs.media.mBloodSmall; // trap_MAT_GetEffect(ammoData[ammoIndex].name, MATERIAL_FLESH); + } + + if (impactEffect) + { + trap_FX_PlayEffectID( impactEffect, origin, dir, -1, 3000 ); + } + + trap_FX_PlayEffectID( cg_weapons[weapon].attack[attack].explosionEffect, origin, dir, -1, 3000 ); +/* + trap_S_StartSound(origin, ENTITYNUM_WORLD, CHAN_WEAPON, cg_weapons[weapon].attack[attack].explosionSound, -1, -1); +*/ + + break; + } +} + +void CG_HandleStickyMissile(centity_t *cent,entityState_t *es,vec3_t dir,int targetEnt) +{ + int hitLoc; + qboolean altFire; + int ammoIndex; + weaponInfo_t *weaponInfo; + qboolean addBoltedMiss; + int missileIndex; + int boltIndex; + + hitLoc=es->otherEntityNum2; + altFire=(cent->currentState.eFlags & EF_ALT_FIRING)!=0; + + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + ammoIndex = weaponData[es->weapon].attack[ATTACK_ALTERNATE].ammoIndex; + } + else + { + ammoIndex = weaponData[es->weapon].attack[ATTACK_NORMAL].ammoIndex; + } + + // Currently the only type of sticky missile is the thrown knife. Others might be added in future though. + addBoltedMiss=qfalse; + switch(es->weapon) + { + case AMMO_KNIFE: + if(altFire) + { + addBoltedMiss=qtrue; + } + break; + + default: + break; + } + + if(addBoltedMiss==qtrue) + { + centity_t* centTarget; + + weaponInfo=&cg_weapons[es->weapon]; + if(!trap_G2_HaveWeGhoul2Models(weaponInfo->weaponG2Model)) + { + return; + } + + centTarget = CG_GetEntity ( targetEnt ); + + missileIndex=trap_G2API_CopySpecificGhoul2Model(weaponInfo->weaponG2Model,0,centTarget->ghoul2,-1); + if(missileIndex!=-1) + { +// Com_Printf("Missile index=%i\n",missileIndex); + boltIndex=trap_G2API_FindBoltIndex(centTarget->ghoul2,0,"rhand"); + if(boltIndex==-1) + { + boltIndex=trap_G2API_AddBolt(centTarget->ghoul2,0,"rhand"); + } + if(boltIndex!=-1) + { +// Com_Printf("Bolt index=%i\n",boltIndex); + if(!trap_G2API_AttachG2Model(centTarget->ghoul2,missileIndex,centTarget->ghoul2,boltIndex,0)) + { +// Com_Printf("Couldn't attach!\n"); + } + } + } + } +} + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer(int tracerEffectID, vec3_t source, vec3_t dest ) +{ + vec3_t forward; + float len; + + // Lower the amount of tracers + float chance = cg_tracerChance.value * 100.0f; + if ( rand()%100 > chance ) + { + return; + } + + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + if (len > 100) + { + VectorMA( source, 100, forward, source ); + trap_FX_PlayEffectID( tracerEffectID, source, forward, -1, -1 ); + } +} + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( + vec3_t end, + int sourceEntityNum, + int weapon, + vec3_t normal, + int fleshEntityNum, + int material, + attackType_t attack + ) +{ + trace_t trace; + int sourceContentType; + int destContentType; + vec3_t start; + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if ( sourceEntityNum >= 0 ) + { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) + { + int tracerEffectID = cg_weapons[weapon].attack[attack].tracerEffect;; + + if (0 != tracerEffectID ) + { + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( end, 0 ); + + // do a complete bubble trail if necessary + if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { + CG_BubbleTrail( start, end, 32 ); + } + // bubble trail from water into air + else if ( ( sourceContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } + // bubble trail from air into water + else if ( ( destContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( trace.endpos, end, 32 ); + } + + // Tracers on enemies only + if ( sourceEntityNum != cg.snap->ps.clientNum || cg.renderingThirdPerson ) + { + CG_Tracer(tracerEffectID, start, end ); + } + } + } + + if ( sourceEntityNum != cg.snap->ps.clientNum ) + { + // Melee attacks should not cause the bullet fly by sound + if ( !weaponData[weapon].attack[attack].melee ) + { + CG_BulletFlyBySound (start, end); + } + } + } + + // impact splash and mark + if ( fleshEntityNum != ENTITYNUM_WORLD ) + { + CG_MissileHitPlayer(weapon, end, normal, fleshEntityNum, qfalse); + } + else + { + CG_MissileHitWall( weapon, end, normal, material, attack ); + } +} + +/* +=============== +CG_StartModelAnims +=============== +*/ +static void CG_StartModelAnims ( TAnimWeapon *aW, playerState_t *ps ) +{ + TAnimInfoWeapon *aIW; + + if(!aW) + { + assert(0); + } + + cg.weaponHideModels=-1; + + // Loop for all models affected by this anim. + aIW=aW->mInfos; + while(aIW) + { + // Ignore if no anims available. + if ( !aIW->mNumChoices ) + { + aIW=aIW->mNext; + continue; + } + + if(!Q_stricmp(aIW->mType,"hands")) + { + // Hands... now which one? + if(!Q_stricmp(aIW->mName,"right")) + { + // Right. + cg.viewWeaponAnim[2] = aIW; + cg.weaponHideModels&=~(1<<2); + } + else if(!Q_stricmp(aIW->mName,"left")) + { + // Left. + cg.viewWeaponAnim[3] = aIW; + cg.weaponHideModels&=~(1<<3); + } + } + else if(!Q_stricmp(aIW->mType,"weaponmodel")) + { + cg.viewWeaponAnim[0] = aIW; + cg.weaponHideModels&=~(1<<0); + } + else if(!Q_stricmp(aIW->mType,"bolton")) + { + // Weapon model. + cg.viewWeaponAnim[4] = aIW; + cg.weaponHideModels&=~(1<<4); + } + else + { + Com_Printf("Anim unknown for model %s\n",aIW->mType); + } + + // Follow link to next model. + aIW=aIW->mNext; + } +} + +/* +=============== +CG_SetWeaponAnim +=============== +*/ +void CG_SetWeaponAnim(int weaponAction,playerState_t *ps) +{ + TAnimWeapon *aW; + aW=BG_GetInviewAnimFromIndex(ps->weapon,weaponAction); + if(aW) + { + CG_StartModelAnims(aW,ps); + } +} + +/* +=============== +CG_WeaponCallback +=============== +*/ +void CG_WeaponCallback ( playerState_t* ps, centity_t* cent, int weapon, int anim, int animChoice, int step ) +{ + int i,j; + TNoteTrack *note; + entityState_t *ent; + weaponInfo_t *weaponInfo; + char *surfName; + int surfFlags; + + ent = ¢->currentState; + + // Make sure the weapon number is valid + if( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + { + Com_Error( ERR_FATAL, "CG_WeaponCallabck: ps->weapon >= WP_NUM_WEAPONS"); + return; + } + + weaponInfo = &cg_weapons[ weapon ]; + + note = BG_GetWeaponNote ( ps, weapon, anim, animChoice, step ); + if ( !note ) + { + return; + } + + if(!strcmp(note->mNote,"fire")) + { + // Callback is type fire = muzzle flash + sound + shell eject. + // TODO. + } + else if(!strcmp(note->mNote,"altfire")) + { + // Callback is type altfire = muzzle flash + sound + shell eject. + // TODO. + } + else + { + // Search the sound list... is it a sound callback? + for(i=0;imNote)) + { + // Play sound. + // FIXME: randomly select sound?? + trap_S_StartSound(NULL,ent->number,CHAN_WEAPON,weaponInfo->otherWeaponSounds[i][0], -1, -1); + break; + } + } + + // Search the surfaces list... is it a surfaces callback? + for(i=0;imNote)) + { + for(j=0;jviewG2Model,weaponInfo->viewG2Indexes[0],surfName,surfFlags); + } + break; + } + } + } +} diff --git a/code/cgame/cgame.bat b/code/cgame/cgame.bat new file mode 100644 index 0000000..1590dea --- /dev/null +++ b/code/cgame/cgame.bat @@ -0,0 +1,83 @@ +@set include= + +del /q vm +mkdir vm +cd vm +set cc=..\..\..\bin\sof2lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_weapons.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_player.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_gametype.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_gametype.c +@if errorlevel 1 goto quit +%cc% ../cg_gore.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_light.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_miscents.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weaponinit.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit +%cc% ../../ui/ui_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_newDraw.c +@if errorlevel 1 goto quit + +..\..\..\bin\sof2asm -f ../cgame +@if errorlevel 1 goto quit + +mkdir "..\..\..\base\vm" +copy *.map "..\..\..\base\vm" +copy *.qvm "..\..\..\base\vm" + +:quit +cd .. diff --git a/code/cgame/cgame.q3asm b/code/cgame/cgame.q3asm new file mode 100644 index 0000000..e0271e9 --- /dev/null +++ b/code/cgame/cgame.q3asm @@ -0,0 +1,35 @@ +-o "sof2mp_cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_gametype +cg_gore +cg_info +cg_light +cg_localents +cg_miscents +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weaponinit +cg_weapons +bg_slidemove +bg_weapons +bg_player +bg_pmove +bg_lib +bg_misc +bg_gametype +q_math +q_shared +ui_shared +cg_newDraw diff --git a/code/cgame/sof2_cgame.def b/code/cgame/sof2_cgame.def new file mode 100644 index 0000000..01861ba --- /dev/null +++ b/code/cgame/sof2_cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/code/cgame/sof2_cgame.dsp b/code/cgame/sof2_cgame.dsp new file mode 100644 index 0000000..d60a579 --- /dev/null +++ b/code/cgame/sof2_cgame.dsp @@ -0,0 +1,760 @@ +# Microsoft Developer Studio Project File - Name="SoF2cgame" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=SoF2cgame - Win32 SH Debug SoF2 +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "sof2_cgame.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "sof2_cgame.mak" CFG="SoF2cgame - Win32 SH Debug SoF2" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SoF2cgame - Win32 Release SoF2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "SoF2cgame - Win32 Debug SoF2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "SoF2cgame - Win32 SH Debug SoF2" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "SoF2cgame___Win32_Release_TA" +# PROP BASE Intermediate_Dir "SoF2cgame___Win32_Release_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "../Release" +# PROP Intermediate_Dir "..\Release/SoF2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDebug SoF2" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /Zi /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_SOF2" /D "CGAME" /YX /FD /c +# SUBTRACT CPP /Fr +# ADD BASE MTL /nologo /D "NDebug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDebug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "NDebug SoF2" +# ADD RSC /l 0x409 /d "NDebug SoF2" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release/SoF2cgamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map:"..\Release/sof2mp_cgamex86.map" /debug /machine:I386 /out:"../Release/sof2mp_cgamex86.dll" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "SoF2cgame___Win32_Debug_TA" +# PROP BASE Intermediate_Dir "SoF2cgame___Win32_Debug_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\Debug" +# PROP Intermediate_Dir "..\Debug\SoF2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_Debug SoF2" /D "_WINDOWS" /FR /YX /FD /c +# ADD CPP /nologo /G5 /MTd /W4 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_SOF2" /D "CGAME" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_Debug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_Debug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "_Debug SoF2" +# ADD RSC /l 0x409 /d "_Debug SoF2" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"..\Debug/SoF2cgamex86.dll" +# SUBTRACT BASE LINK32 /profile /nodefaultlib +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /base:"0x30000000" /subsystem:windows /dll /map:"..\Debug\sof2mp_cgamex86.map" /debug /machine:I386 /def:".\SoF2_cgame.def" /out:"..\Debug\sof2mp_cgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "SoF2cgame___Win32_SH_Debug_SoF2" +# PROP BASE Intermediate_Dir "SoF2cgame___Win32_SH_Debug_SoF2" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "SoF2cgame___Win32_SH_Debug_SoF2" +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W4 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_SOF2" /FR /YX /FD /c +# ADD CPP /nologo /G5 /MTd /W4 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "MISSIONPACK" /D "_SOF2" /D "MEM_DEBUG" /D "CGAME" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_Debug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_Debug SoF2" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x409 /d "_Debug SoF2" +# ADD RSC /l 0x409 /d "_Debug SoF2" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map:"..\Debug\sof2mp_cgamex86.map" /debug /machine:I386 /def:".\SoF2_cgame.def" /out:"..\Debug\sof2mp_cgamex86.dll" +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /pdb:"..\SHDebug/sof2mp_cgamex86.pdb" /map:"..\SHDebug\sof2mp_cgamex86.map" /debug /machine:I386 /def:".\SoF2_cgame.def" /out:"..\SHDebug\sof2mp_cgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "SoF2cgame - Win32 Release SoF2" +# Name "SoF2cgame - Win32 Debug SoF2" +# Name "SoF2cgame - Win32 SH Debug SoF2" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\game\bg_gametype.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_lib.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP BASE Exclude_From_Build 1 +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_misc.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_player.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_pmove.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_slidemove.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_weapons.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_consolecmds.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_draw.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_drawtools.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_effects.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_ents.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_event.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_gametype.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_gore.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_info.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_light.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_localents.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_main.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_miscents.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_newDraw.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_players.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_playerstate.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_predict.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_scoreboard.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_servercmds.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_snapshot.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_syscalls.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_view.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_weaponinit.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_weapons.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\q_math.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\ui\ui_shared.c + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP Intermediate_Dir "..\SHDebug\SoF2cgame" + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\animtable.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_local.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_public.h +# End Source File +# Begin Source File + +SOURCE=..\game\bg_weapons.h +# End Source File +# Begin Source File + +SOURCE=.\cg_lights.h +# End Source File +# Begin Source File + +SOURCE=.\cg_local.h +# End Source File +# Begin Source File + +SOURCE=.\cg_public.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\disablewarnings.h +# End Source File +# Begin Source File + +SOURCE=..\ghoul2\G2.h +# End Source File +# Begin Source File + +SOURCE=..\ghoul2\G2_gore_shared.h +# End Source File +# Begin Source File + +SOURCE=..\game\g_local.h +# End Source File +# Begin Source File + +SOURCE=..\game\g_public.h +# End Source File +# Begin Source File + +SOURCE=..\game\g_team.h +# End Source File +# Begin Source File + +SOURCE=..\ui\keycodes.h +# End Source File +# Begin Source File + +SOURCE=..\..\ui\menudef.h +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.h +# End Source File +# Begin Source File + +SOURCE=..\game\surfaceflags.h +# End Source File +# Begin Source File + +SOURCE=..\qcommon\tags.h +# End Source File +# Begin Source File + +SOURCE=.\tr_types.h +# End Source File +# Begin Source File + +SOURCE=..\ui\ui_shared.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\cgame.bat +# End Source File +# Begin Source File + +SOURCE=.\cgame.q3asm +# End Source File +# Begin Source File + +SOURCE=.\SoF2_cgame.def + +!IF "$(CFG)" == "SoF2cgame - Win32 Release SoF2" + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 Debug SoF2" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "SoF2cgame - Win32 SH Debug SoF2" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# End Target +# End Project diff --git a/code/cgame/tr_types.h b/code/cgame/tr_types.h new file mode 100644 index 0000000..07fd8cd --- /dev/null +++ b/code/cgame/tr_types.h @@ -0,0 +1,338 @@ +// Copyright (C) 2001-2002 Raven Software. +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing +#define MAX_MINI_ENTITIES 1024 + +// renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 8 // for view weapon Z crunching +#define RF_NOSHADOW 64 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +#define RF_FORKED 0x00001000 // override lightning to have forks +#define RF_TAPERED 0x00002000 // lightning tapers +#define RF_GROW 0x00004000 // lightning grows from start to end during its life +#define RF_NO_FOG 0x00008000 // no fog for g2 models + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_PROJECTION2D 2 +#define RDF_HYPERSPACE 4 // teleportation effect + +typedef byte color4ub_t[4]; + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_ORIENTED_QUAD, + RT_BEAM, + RT_ELECTRICITY, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + RT_LINE, + RT_ORIENTEDLINE, + RT_CYLINDER, + RT_ENT_CHAIN, + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct miniRefEntity_s +{ + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; // size 2 for RT_CYLINDER or number of verts in RT_ELECTRICITY + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + +} miniRefEntity_t; + +#pragma warning (disable : 4201 ) +typedef struct { + // this stucture must remain identical as the miniRefEntity_t + // + // + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + // + // + // end miniRefEntity_t + + // + // + // specific full refEntity_t data + // + // + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + // previous data for frame interpolation + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + + // texturing + union + { +// int skinNum; // inline skin index +// ivec3_t terxelCoords; // coords of patch for RT_TERXELS + struct + { + int miniStart; + int miniCount; + } uMini; + } uRefEnt; + + // extra sprite information + union { + struct + { + float rotation; + float radius; + byte vertRGBA[4][4]; + } sprite; + struct + { + float width; + float width2; + float stscale; + } line; + struct // that whole put-the-opening-brace-on-the-same-line-as-the-beginning-of-the-definition coding style is fecal + { + float width; + vec3_t control1; + vec3_t control2; + } bezier; + struct + { + float width; + float width2; + float stscale; + float height; + float bias; + qboolean wrap; + } cylinder; + struct + { + float width; + float deviation; + float stscale; + qboolean wrap; + qboolean taper; + } electricity; + } data; + + vec3_t angles; // rotation angles - used for Ghoul2 + vec3_t modelScale; // axis scale for models + void *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly + +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; + + vec3_t viewangles; + +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int tfSolidCompressed; + float tfSolidCompressedBPT; + int tfAlphaCompressed; + float tfAlphaCompressedBPT; + int tfSolidUncompressed; + float tfSolidUncompressedBPT; + int tfAlphaUncompressed; + float tfAlphaUncompressedBPT; + int tfLightmap; + float tfLightmapBPT; + int tfCinematic; // Specially for the Voodoo4 - glTexImage2D can only handle 16 bit + float tfCinematicBPT; + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + qboolean textureEnvAddAvailable; + qboolean textureFilterAnisotropicAvailable; + qboolean clampToEdgeAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean smpActive; // dual processor +} glconfig_t; + + +typedef enum +{ + VO_NONE = 0, + VO_NIGHTVISION, // parm1 = visible distance, parm2 = not used + VO_INFRARED, // parm1 = distance to see heat, parm2 = distance you can see them through walls +} visual_t; + + +#if !defined _WIN32 + +#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" +#define OPENGL_DRIVER_NAME "libGL.so" + +#else + +#define _3DFX_DRIVER_NAME "3dfxvgl" +#define OPENGL_DRIVER_NAME "opengl32" + +#endif // !defined _WIN32 + + +#endif // __TR_TYPES_H diff --git a/code/game/ai_main.c b/code/game/ai_main.c new file mode 100644 index 0000000..77a6b71 --- /dev/null +++ b/code/game/ai_main.c @@ -0,0 +1,5511 @@ +// Copyright (C) 2001-2002 Raven Software. +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * $Author: Mrelusive $ + * $Revision: 35 $ + * $Modtime: 6/06/01 1:11p $ + * $Date: 6/06/01 12:06p $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +/* +#define BOT_CTF_DEBUG 1 +*/ + +#define MAX_PATH 144 + +#define BOT_THINK_TIME 0 + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// + +boteventtracker_t gBotEventTracker[MAX_CLIENTS]; + +//rww - new bot cvars.. +#ifdef _DEBUG +vmCvar_t bot_debugmessages; +#endif + +vmCvar_t bot_attachments; +vmCvar_t bot_camp; +vmCvar_t bot_pause; + +vmCvar_t bot_wp_info; +vmCvar_t bot_wp_edit; +vmCvar_t bot_wp_clearweight; +vmCvar_t bot_wp_distconnect; +vmCvar_t bot_wp_visconnect; +//end rww + +wpobject_t *flagRed; +wpobject_t *oFlagRed; +wpobject_t *flagBlue; +wpobject_t *oFlagBlue; + +gentity_t *eFlagRed; +gentity_t *droppedRedFlag; +gentity_t *eFlagBlue; +gentity_t *droppedBlueFlag; + +char *ctfStateNames[] = { + "CTFSTATE_NONE", + "CTFSTATE_ATTACKER", + "CTFSTATE_DEFENDER", + "CTFSTATE_RETRIEVAL", + "CTFSTATE_GUARDCARRIER", + "CTFSTATE_GETFLAGHOME", + "CTFSTATE_MAXCTFSTATES" +}; + +char *ctfStateDescriptions[] = { + "I'm not occupied", + "I'm attacking the enemy's base", + "I'm defending our base", + "I'm getting our flag back", + "I'm escorting our flag carrier", + "I've got the enemy's flag" +}; + +char *teamplayStateDescriptions[] = { + "I'm not occupied", + "I'm following my squad commander", + "I'm assisting my commanding", + "I'm attempting to regroup and form a new squad" +}; + +void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs) +{ + switch (ordernum) + { + case 0: + if (bs->squadLeader == ent) + { + bs->teamplayState = 0; + bs->squadLeader = NULL; + } + break; + case TEAMPLAYSTATE_FOLLOWING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + case TEAMPLAYSTATE_ASSISTING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + default: + bs->teamplayState = ordernum; + break; + } +} + +void BotReportStatus(bot_state_t *bs) +{ + if ( level.gametypeData->teams ) + { + trap_EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]); + } +} + +void BotOrder(gentity_t *ent, int clientnum, int ordernum) +{ + int stateMin = 0; + int stateMax = 0; + int i = 0; + + if (!ent || !ent->client) + { + return; + } + + if (clientnum != -1 && !botstates[clientnum]) + { + return; + } + + if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum])) + { + return; + } + + if ( !level.gametypeData->teams ) + { + return; + } + +/* + if (level.gametype == GT_CTF) + { + stateMin = CTFSTATE_NONE; + stateMax = CTFSTATE_MAXCTFSTATES; + } + else if (level.gametype == GT_TDM) + { + stateMin = TEAMPLAYSTATE_NONE; + stateMax = TEAMPLAYSTATE_MAXTPSTATES; + } +*/ + + if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax) + { + return; + } + + if (clientnum != -1) + { + if (ordernum == -1) + { + BotReportStatus(botstates[clientnum]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]); + botstates[clientnum]->state_Forced = ordernum; + botstates[clientnum]->chatObject = ent; + botstates[clientnum]->chatAltObject = NULL; + if (BotDoChat(botstates[clientnum], "OrderAccepted", 1)) + { + botstates[clientnum]->chatTeam = 1; + } + } + } + else + { + while (i < MAX_CLIENTS) + { + if (botstates[i] && OnSameTeam(ent, &g_entities[i])) + { + if (ordernum == -1) + { + BotReportStatus(botstates[i]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[i]); + botstates[i]->state_Forced = ordernum; + botstates[i]->chatObject = ent; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "OrderAccepted", 0)) + { + botstates[i]->chatTeam = 1; + } + } + } + + i++; + } + } +} + +int BotGetWeaponRange(bot_state_t *bs); +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent); + +void ExitLevel( void ); + +void QDECL BotAI_Print(int type, char *fmt, ...) { return; } + +int IsTeamplay(void) +{ + return level.gametypeData->teams; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + factor = bs->skills.turnspeed_combat*bs->settings.skill; + } + else + { + factor = bs->skills.turnspeed; + } + + if (factor > 1) + { + factor = 1; + } + if (factor < 0.001) + { + factor = 0.001f; + } + + maxchange = bs->skills.maxturn; + + //if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) + { + ucmd->buttons = BUTTON_ATTACK; + } + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_ATTACK; +// if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; +// if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; +#ifdef BOT_USE_HOLDABLE + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; +#endif + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + + if (useTime < level.time && Q_irand(1, 10) < 5) + { //for now just hit use randomly in case there's something useable around + ucmd->buttons |= BUTTON_USE; + } +#if 0 +// Here's an interesting bit. The bots in TA used buttons to do additional gestures. +// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2. +// We can always add some back in if we want though. + if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; + if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; + if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; + if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; + if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; + if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; +#endif //0 + + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + trap_EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) + { + // IF already trying to respawn or a ghost then cancel the respawn + if ((bs->lastucmd.buttons & BUTTON_ATTACK) || (bs->cur_ps.pm_flags&PMF_GHOST)) + { + bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + } + + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < FloatTime()) { + trap_BotUpdateEntityItems(); + regularupdate_time = FloatTime() + 0.3; + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; +#ifdef _DEBUG + int start = 0; + int end = 0; +#endif + + trap_EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + RemoveColorEscapeSequences( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + +#ifdef _DEBUG + start = trap_Milliseconds(); +#endif + StandardBotAI(bs, thinktime); +#ifdef _DEBUG + end = trap_Milliseconds(); + + trap_Cvar_Update(&bot_debugmessages); + + if (bot_debugmessages.integer) + { + Com_Printf("Single AI frametime: %i\n", (end - start)); + } +#endif + + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = BOT_THINK_TIME * botnum / numbots; + botnum++; + } +} + +int PlayersInGame(void) +{ + int i = 0; + gentity_t *ent; + int pl = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED) + { + pl++; + } + + i++; + } + + return pl; +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + bot_state_t *bs; + + if (!botstates[client]) botstates[client] = B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t)); + //rww - G_Alloc bad! B_Alloc good. + + memset(botstates[client], 0, sizeof(bot_state_t)); + + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + + bs->client = client; //need to know the client number before doing personality stuff + + //initialize weapon weight defaults.. + bs->botWeaponWeights[WP_NONE] = 0; + bs->botWeaponWeights[WP_KNIFE] = 1; + bs->botWeaponWeights[WP_M1911A1_PISTOL] = 3; + bs->botWeaponWeights[WP_USSOCOM_PISTOL] = 2; + bs->botWeaponWeights[WP_M4_ASSAULT_RIFLE] = 10; + bs->botWeaponWeights[WP_AK74_ASSAULT_RIFLE] = 9; + bs->botWeaponWeights[WP_M60_MACHINEGUN] = 11; + bs->botWeaponWeights[WP_MICRO_UZI_SUBMACHINEGUN] = 8; + bs->botWeaponWeights[WP_M3A1_SUBMACHINEGUN] = 7; + bs->botWeaponWeights[WP_MSG90A1] = 11; + bs->botWeaponWeights[WP_USAS_12_SHOTGUN] = 12; + bs->botWeaponWeights[WP_M590_SHOTGUN] = 13; + bs->botWeaponWeights[WP_MM1_GRENADE_LAUNCHER] = 8; + bs->botWeaponWeights[WP_RPG7_LAUNCHER] = 16; + bs->botWeaponWeights[WP_M67_GRENADE] = 6; + bs->botWeaponWeights[WP_M84_GRENADE] = 6; + bs->botWeaponWeights[WP_F1_GRENADE] = 6; + bs->botWeaponWeights[WP_L2A2_GRENADE] = 5; + bs->botWeaponWeights[WP_MDN11_GRENADE] = 5; + bs->botWeaponWeights[WP_SMOHG92_GRENADE] = 2; + bs->botWeaponWeights[WP_ANM14_GRENADE] = 2; + bs->botWeaponWeights[WP_M15_GRENADE] = 2; + + BotUtilizePersonality(bs); + + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + + bs->inuse = qtrue; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = trap_BotAllocMoveState(); + numbots++; + + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + + if (PlayersInGame()) + { //don't talk to yourself + BotDoChat(bs, "GeneralGreetings", 0); + } + + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + // + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, weaponstate; + bot_settings_t settings; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + movestate = bs->ms; + goalstate = bs->gs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) trap_BotResetMoveState(bs->ms); + if (bs->gs) trap_BotResetGoalState(bs->gs); + if (bs->ws) trap_BotResetWeaponState(bs->ws); + if (bs->gs) trap_BotResetAvoidGoals(bs->gs); + if (bs->ms) trap_BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + return qtrue; +} + +//rww - bot ai +int OrgVisible(vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 1; + } + + return 0; +} + +int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 1; + } + + return 0; +} + +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +int CheckForFunc(vec3_t org, int ignore) +{ + gentity_t *fent; + vec3_t under; + trace_t tr; + + VectorCopy(org, under); + + under[2] -= 64; + + trap_Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + fent = &g_entities[tr.entityNum]; + + if (!fent) + { + return 0; + } + + if (strstr(fent->classname, "func_")) + { + return 1; //there's a func brush here + } + + return 0; +} + +int GetNearestVisibleWP(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + bestdist = 800;//99999; + //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^ + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -1; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 1; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +//wpDirection +//0 == FORWARD +//1 == BACKWARD + +int PassWayCheck(bot_state_t *bs, int windex) +{ + if (!gWPArray[windex] || !gWPArray[windex]->inuse) + { + return 0; + } + + if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD)) + { + return 0; + } + else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK)) + { + return 0; + } + + return 1; +} + +float TotalTrailDistance(int start, int end, bot_state_t *bs) +{ + int beginat; + int endat; + float distancetotal; + float gdif = 0; + + distancetotal = 0; + + if (start > end) + { + beginat = end; + endat = start; + } + else + { + beginat = start; + endat = end; + } + + while (beginat < endat) + { + if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse) + { + return -1; //error + } + + if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) || + (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD)) + { + return -1; + } + + if (gWPArray[beginat]->forceJumpTo) + { + if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2]) + { + gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2]; + } + + if (gdif) + { + // if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo) + // { + // return -1; + // } + } + } + + /* if (bs->wpCurrent && gWPArray[windex]->forceJumpTo && + gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) && + bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo) + { + return -1; + }*/ + + distancetotal += gWPArray[beginat]->disttonext; + + beginat++; + } + + return distancetotal; +} + +void CheckForShorterRoutes(bot_state_t *bs, int newwpindex) +{ + float bestlen; + float checklen; + int bestindex; + int i; + + i = 0; + + if (!bs->wpDestination) + { + return; + } + + if (newwpindex < bs->wpDestination->index) + { + bs->wpDirection = 0; + } + else if (newwpindex > bs->wpDestination->index) + { + bs->wpDirection = 1; + } + + if (bs->wpSwitchTime > level.time) + { + return; + } + + if (!gWPArray[newwpindex]->neighbornum) + { + return; + } + + bestindex = newwpindex; + bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs); + + while (i < gWPArray[newwpindex]->neighbornum) + { + checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs); + + if (checklen < bestlen-64 || bestlen == -1) + { + if (!gWPArray[newwpindex]->neighbors[i].forceJumpTo) + { + bestlen = checklen; + bestindex = gWPArray[newwpindex]->neighbors[i].num; + } + } + + i++; + } + + if (bestindex != newwpindex && bestindex != -1) + { + bs->wpCurrent = gWPArray[bestindex]; + bs->wpSwitchTime = level.time + 3000; + } +} + +void WPConstantRoutine(bot_state_t *bs) +{ + if (!bs->wpCurrent) + { + return; + } + + if (bs->wpCurrent->flags & WPFLAG_DUCK) + { + bs->duckTime = level.time + 100; + } +} + +qboolean BotCTFGuardDuty(bot_state_t *bs) +{ +/* + if (level.gametype != GT_CTF) + { + return qfalse; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + return qtrue; + } +*/ + return qfalse; +} + +void WPTouchRoutine(bot_state_t *bs) +{ + int lastNum; + + if (!bs->wpCurrent) + { + return; + } + + bs->wpTravelTime = level.time + 10000; + + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { + bs->noUseTime = level.time + 4000; + } + +#ifdef FORCEJUMP_INSTANTMETHOD + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#else + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#endif + + trap_Cvar_Update(&bot_camp); + + if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) && + bs->cur_ps.weapon != WP_KNIFE) + { //if we're a camper and a chicken then camp + if (bs->wpDirection) + { + lastNum = bs->wpCurrent->index+1; + } + else + { + lastNum = bs->wpCurrent->index-1; + } + + if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time) + { + bs->isCamping = level.time + rand()%15000 + 30000; + bs->wpCamping = bs->wpCurrent; + bs->wpCampingTo = gWPArray[lastNum]; + + if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND) + { + bs->campStanding = qtrue; + } + else + { + bs->campStanding = qfalse; + } + } + + } + else if ((bs->cur_ps.weapon == WP_KNIFE) && + bs->isCamping > level.time) + { + bs->isCamping = 0; + bs->wpCampingTo = NULL; + bs->wpCamping = NULL; + } + + if (bs->wpDestination) + { + if (bs->wpCurrent->index == bs->wpDestination->index) + { + bs->wpDestination = NULL; + + if (bs->runningLikeASissy) + { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed + bs->destinationGrabTime = level.time + 500; + } + else + { + bs->destinationGrabTime = level.time + 3500; + } + } + else + { + CheckForShorterRoutes(bs, bs->wpCurrent->index); + } + } +} + +void MoveTowardIdealAngles(bot_state_t *bs) +{ + VectorCopy(bs->goalAngles, bs->ideal_viewangles); +} + +#define BOT_STRAFE_AVOIDANCE + +#ifdef BOT_STRAFE_AVOIDANCE +#define STRAFEAROUND_RIGHT 1 +#define STRAFEAROUND_LEFT 2 + +int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto) +{ + vec3_t playerMins = {-15, -15, /*-24*/-8}; + vec3_t playerMaxs = {15, 15, 32}; + vec3_t from, to; + vec3_t dirAng, dirDif; + vec3_t forward, right; + trace_t tr; + + if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE) + { //don't do this in the air, it can be.. dangerous. + return 0; + } + + VectorSubtract(traceto, bs->origin, dirAng); + VectorNormalize(dirAng); + vectoangles(dirAng, dirAng); + + if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 || + AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60) + { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway + return 0; + } + + VectorCopy(bs->origin, from); + VectorCopy(traceto, to); + + VectorSubtract(to, from, dirDif); + VectorNormalize(dirDif); + vectoangles(dirDif, dirDif); + + AngleVectors(dirDif, forward, 0, 0); + + to[0] = from[0] + forward[0]*32; + to[1] = from[1] + forward[1]*32; + to[2] = from[2] + forward[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + AngleVectors(dirAng, 0, right, 0); + + from[0] += right[0]*32; + from[1] += right[1]*32; + from[2] += right[2]*16; + + to[0] += right[0]*32; + to[1] += right[1]*32; + to[2] += right[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_RIGHT; + } + + from[0] -= right[0]*64; + from[1] -= right[1]*64; + from[2] -= right[2]*64; + + to[0] -= right[0]*64; + to[1] -= right[1]*64; + to[2] -= right[2]*64; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_LEFT; + } + + return 0; +} +#endif + +int BotTrace_Jump(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + int orTr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + orTr = tr.entityNum; + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 41; + traceto_mod[2] += 41; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time) + { + return 0; //so bots don't try to jump over each other at the same time + } + + if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)) + { + return 0; + } + + return 1; + } + + return 0; +} + +int BotTrace_Duck(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -23; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 0; + } + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 31;//33; + traceto_mod[2] += 31;//33; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en) +{ + if (!bs || !en) + { + return 0; + } + + if (!en->client) + { + return 0; + } + + if (en->health < 1) + { + return 0; + } + + if (!en->takedamage) + { + return 0; + } + + if (en->client) + { + if (en->client->ps.pm_type != PM_NORMAL ) + { + return 0; + } + + if ( G_IsClientSpectating ( en->client ) ) + { + return 0; + } + } + + if (!en->s.solid) + { + return 0; + } + + if (bs->client == en->s.number) + { + return 0; + } + + if (OnSameTeam(&g_entities[bs->client], en)) + { + return 0; + } + + /* + if (en->client && en->client->pers.connected != CON_CONNECTED) + { + return 0; + } + */ + + return 1; +} + +void BotDamageNotification(gclient_t *bot, gentity_t *attacker) +{ + bot_state_t *bs; + bot_state_t *bs_a; + int i; + + if (!bot || !attacker || !attacker->client) + { + return; + } + + bs_a = botstates[attacker->s.number]; + + if (bs_a) + { + bs_a->lastAttacked = &g_entities[bot->ps.clientNum]; + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + i != bs_a->client && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + else //got attacked by a real client, so no one gets rights to lastAttacked + { + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + + bs = botstates[bot->ps.clientNum]; + + if (!bs) + { + return; + } + + bs->lastHurt = attacker; + + if (bs->currentEnemy) + { + return; + } + + if (!PassStandardEnemyChecks(bs, attacker)) + { + return; + } + + if (PassLovedOneCheck(bs, attacker)) + { + bs->currentEnemy = attacker; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } +} + +int BotCanHear(bot_state_t *bs, gentity_t *en, float endist) +{ + float minlen; + + if (!en || !en->client) + { + return 0; + } + + /* + if (en && en->client && en->client->ps.otherSoundTime > level.time) + { + minlen = en->client->ps.otherSoundLen; + goto checkStep; + } + */ + + /* + if (en && en->client && en->client->ps.footstepTime > level.time) + { + minlen = 256; + goto checkStep; + } + */ + + if (gBotEventTracker[en->s.number].eventTime < level.time) + { + return 0; + } + + switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)]) + { + case EV_GLOBAL_SOUND: + minlen = 256; + break; + case EV_FIRE_WEAPON: + case EV_ALT_FIRE: + minlen = 512; + break; + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_FOOTSTEP: + case EV_FOOTWADE: + minlen = 256; + break; + case EV_JUMP: + minlen = 256; + break; + default: + minlen = 999999; + break; + } + + if (endist <= minlen) + { + return 1; + } + + return 0; +} + +void UpdateEventTracker(void) +{ + int i; + + i = 0; + + while (i < MAX_CLIENTS) + { + if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence) + { //updated event + gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence; + gBotEventTracker[i].events[0] = level.clients[i].ps.events[0]; + gBotEventTracker[i].events[1] = level.clients[i].ps.events[1]; + gBotEventTracker[i].eventTime = level.time + 0.5; + } + + i++; + } +} + +int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) + { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) + { + if (diff > 180.0) + { + diff -= 360.0; + } + } + else + { + if (diff < -180.0) + { + diff += 360.0; + } + } + if (diff > 0) + { + if (diff > fov * 0.5) + { + return 0; + } + } + else + { + if (diff < -fov * 0.5) + { + return 0; + } + } + } + return 1; +} + +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent) +{ + int i; + bot_state_t *loved; + + if (!bs->lovednum) + { + return 1; + } + + i = 0; + + if (!botstates[ent->s.number]) + { //not a bot + return 1; + } + + trap_Cvar_Update(&bot_attachments); + + if (!bot_attachments.integer) + { + return 1; + } + + loved = botstates[ent->s.number]; + + while (i < bs->lovednum) + { + if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0) + { + if (!IsTeamplay() && bs->loved[i].level < 2) + { //if FFA and level of love is not greater than 1, just don't care + return 1; + } + else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2) + { //is teamplay, but not on same team and level < 2 + return 1; + } + else + { + return 0; + } + } + + i++; + } + + return 1; +} + +int ScanForEnemies(bot_state_t *bs) +{ + vec3_t a; + float distcheck; + float closest; + int bestindex; + int i; + float hasEnemyDist = 0; + + closest = 999999; + i = 0; + bestindex = -1; + + if (bs->currentEnemy) + { + hasEnemyDist = bs->frame_Enemy_Len; + } + + while (i <= MAX_CLIENTS) + { + if (i != bs->client && g_entities[i].client && !OnSameTeam(&g_entities[bs->client], &g_entities[i]) && PassStandardEnemyChecks(bs, &g_entities[i]) && trap_InPVS(g_entities[i].client->ps.origin, bs->eye) && PassLovedOneCheck(bs, &g_entities[i])) + { + VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a); + distcheck = VectorLength(a); + vectoangles(a, a); + + if (distcheck < closest && ((InFieldOfVision(bs->viewangles, 90, a) /*&& !BotMindTricked(bs->client, i)*/) || BotCanHear(bs, &g_entities[i], distcheck)) && OrgVisible(bs->eye, g_entities[i].client->ps.origin, -1)) + { + if (!hasEnemyDist || distcheck < (hasEnemyDist - 128)) + { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out + closest = distcheck; + bestindex = i; + } + } + } + i++; + } + + return bestindex; +} + +int WaitingForNow(bot_state_t *bs, vec3_t goalpos) +{ //checks if the bot is doing something along the lines of waiting for an elevator to raise up + vec3_t xybot, xywp, a; + + if (!bs->wpCurrent) + { + return 0; + } + + if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] || + (int)goalpos[1] != (int)bs->wpCurrent->origin[1] || + (int)goalpos[2] != (int)bs->wpCurrent->origin[2]) + { + return 0; + } + + VectorCopy(bs->origin, xybot); + VectorCopy(bs->wpCurrent->origin, xywp); + + xybot[2] = 0; + xywp[2] = 0; + + VectorSubtract(xybot, xywp, a); + + if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100) + { + if (CheckForFunc(bs->origin, bs->client)) + { + return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so. + } + } + else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 && + CheckForFunc(bs->origin, bs->client)) + { + bs->noUseTime = level.time + 2000; + } + + return 0; +} + +int BotGetWeaponRange(bot_state_t *bs) +{ + switch (weaponData[bs->cur_ps.weapon].category) + { + case CAT_KNIFE: + return BWEAPONRANGE_MELEE; + case CAT_PISTOL: + return BWEAPONRANGE_MID; //short + case CAT_SHOTGUN: + return BWEAPONRANGE_MID; //short + case CAT_SUB: + return BWEAPONRANGE_MID; + case CAT_ASSAULT: + return BWEAPONRANGE_MID; + case CAT_SNIPER: + return BWEAPONRANGE_LONG; + case CAT_HEAVY: + return BWEAPONRANGE_LONG; + case CAT_GRENADE: + return BWEAPONRANGE_MID; //short + default: + return BWEAPONRANGE_MID; + } +} + +int BotIsAChickenWuss(bot_state_t *bs) +{ + int bWRange; + + if (bs->chickenWussCalculationTime > level.time) + { + return 2; //don't want to keep going between two points... + } + + bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME; + + if (g_entities[bs->client].health < BOT_RUN_HEALTH) + { + return 1; + } + + bWRange = BotGetWeaponRange(bs); + + if (bWRange == BWEAPONRANGE_MELEE) + { + if (!bs->meleeSpecialist) + { + return 1; + } + } + + if (bs->cur_ps.weapon < WP_USAS_12_SHOTGUN) + { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use + return 1; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.weapon == WP_KNIFE && + bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_KNIFE) + { //if close to an enemy with a knife and not using a knife, then try to back off + return 1; + } + + //didn't run, reset the timer + bs->chickenWussCalculationTime = 0; + + return 0; +} + +gentity_t *GetNearestBadThing(bot_state_t *bs) +{ + int i = 0; + float glen; + vec3_t hold; + int bestindex = 0; + float bestdist = 800; //if not within a radius of 800, it's no threat anyway + int foundindex = 0; + float factor = 0; + gentity_t *ent; + trace_t tr; + + while (i < MAX_GENTITIES) + { + ent = &g_entities[i]; + + if ( (ent && + !ent->client && + ent->inuse && + ent->damage && + /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/ + ent->s.weapon && + ent->splashDamage) ) + { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles. + //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes. + VectorSubtract(bs->origin, ent->r.currentOrigin, hold); + glen = VectorLength(hold); + + //if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE && + // ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE) + if (weaponData[ent->s.weapon].category != CAT_GRENADE) + { + factor = 0.5; + } + else + { + factor = 1; + } + + if (ent->s.weapon == WP_RPG7_LAUNCHER && + (ent->r.ownerNum == bs->client || + (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS && + g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) ) + { //don't be afraid of your own rockets or your teammates' rockets + factor = 0; + } + + if (glen < bestdist*factor && trap_InPVS(bs->origin, ent->s.pos.trBase)) + { + trap_Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == ent->s.number) + { + bestindex = i; + bestdist = glen; + foundindex = 1; + } + } + } + + i++; + } + + if (foundindex) + { + bs->dontGoBack = level.time + 1500; + return &g_entities[bestindex]; + } + else + { + return NULL; + } +} + +int BotDefendFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.team == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.team == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GUARD_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +int BotGetEnemyFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.team == TEAM_RED) + { + flagPoint = flagBlue; + } + else if (level.clients[bs->client].sess.team == TEAM_BLUE) + { + flagPoint = flagRed; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +int BotGetFlagBack(bot_state_t *bs) +{ +#ifdef BOT_KNOW_CTF + int i = 0; + int myFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +#else + return 0; +#endif +} + +int BotGuardFlagCarrier(bot_state_t *bs) +{ +#ifdef BOT_KNOW_CTF + int i = 0; + int enemyFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +#else + return 0; +#endif +} + +int BotGetFlagHome(bot_state_t *bs) +{ +#ifdef BOT_KNOW_CTF + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +#else + return 0; +#endif +} + +void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team) +{ //get the nearest possible waypoint to the flag since it's not in its original position +#ifdef BOT_KNOW_CTF + int i = 0; + vec3_t a, mins, maxs; + float bestdist; + float testdist; + int bestindex = 0; + int foundindex = 0; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a); + + bestdist = VectorLength(a); + + if (bestdist <= WP_KEEP_FLAG_DIST) + { + trap_Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { //this point is good + return; + } + } + + while (i < gWPNum) + { + VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a); + testdist = VectorLength(a); + + if (testdist < bestdist) + { + trap_Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { + foundindex = 1; + bestindex = i; + bestdist = testdist; + } + } + + i++; + } + + if (foundindex) + { + if (team == TEAM_RED) + { + flagRed = gWPArray[bestindex]; + } + else + { + flagBlue = gWPArray[bestindex]; + } + } +#endif +} + +int CTFTakesPriority(bot_state_t *bs) +{ +/* +#ifdef BOT_KNOW_CTF + gentity_t *ent = NULL; + int enemyFlag = 0; + int myFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + int i = 0; + int idleWP; + int dosw = 0; + wpobject_t *dest_sw = NULL; +#ifdef BOT_CTF_DEBUG + vec3_t t; + + G_Printf("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]); +#endif + + if (level.gametype != GT_CTF) + { + return 0; + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME) + { //get the nearest weapon laying around base before heading off for battle + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + if (bs->wpDestSwitchTime < level.time) + { + bs->wpDestination = gWPArray[idleWP]; + } + return 1; + } + } + else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF && + bs->wpDestination && bs->wpDestination->weight) + { + dest_sw = bs->wpDestination; + dosw = 1; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + if (!flagRed || !flagBlue || + !flagRed->inuse || !flagBlue->inuse || + !eFlagRed || !eFlagBlue) + { + return 0; + } + +#ifdef BOT_CTF_DEBUG + VectorCopy(flagRed->origin, t); + t[2] += 128; + G_TestLine(flagRed->origin, t, 0x0000ff, 500); + + VectorCopy(flagBlue->origin, t); + t[2] += 128; + G_TestLine(flagBlue->origin, t, 0x0000ff, 500); +#endif + + if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED); + } + else + { + flagRed = oFlagRed; + } + + if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE); + } + else + { + flagBlue = oFlagBlue; + } + + if (!bs->ctfState) + { + return 0; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + if (bs->cur_ps.powerups[enemyFlag]) + { + if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { + bs->ctfState = CTFSTATE_RETRIEVAL; + } + else + { + bs->ctfState = CTFSTATE_GETFLAGHOME; + } + } + else if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + bs->ctfState = 0; + } + + if (bs->state_Forced) + { + bs->ctfState = bs->state_Forced; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + if (BotDefendFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_ATTACKER) + { + if (BotGetEnemyFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_RETRIEVAL) + { + if (BotGetFlagBack(bs)) + { + goto success; + } + else + { //can't find anyone on another team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GUARDCARRIER) + { + if (BotGuardFlagCarrier(bs)) + { + goto success; + } + else + { //can't find anyone on our team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + if (BotGetFlagHome(bs)) + { + goto success; + } + } + + return 0; + +success: + if (dosw) + { //allow ctf code to run, but if after a particular item then keep going after it + bs->wpDestination = dest_sw; + } + + return 1; +#else + return 0; +#endif +*/ + return 0; +} + +int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2) + { + return 1; + } + + return 0; +} + +int BotHasAssociated(bot_state_t *bs, wpobject_t *wp) +{ + gentity_t *as; + + if (wp->associated_entity == ENTITYNUM_NONE) + { //make it think this is an item we have so we don't go after nothing + return 1; + } + + as = &g_entities[wp->associated_entity]; + + if (!as || !as->item) + { + return 0; + } + + if (as->item->giType == IT_WEAPON) + { + if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } +#ifdef BOT_USE_HOLDABLE + else if (as->item->giType == IT_HOLDABLE) + { + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } +#endif + else if (as->item->giType == IT_AMMO) + { + if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack + { + return 1; + } + + return 0; + } + + return 0; +} + +int GetBestIdleGoal(bot_state_t *bs) +{ + int i = 0; + int highestweight = 0; + int desiredindex = -1; + int dist_to_weight = 0; + int traildist; + + if (!bs->wpCurrent) + { + return -1; + } + + if (bs->isCamper != 2) + { + if (bs->randomNavTime < level.time) + { + if (Q_irand(1, 10) < 5) + { + bs->randomNav = 1; + } + else + { + bs->randomNav = 0; + } + + bs->randomNavTime = level.time + Q_irand(5000, 15000); + } + } + + if (bs->randomNav) + { //stop looking for items and/or camping on them + return -1; + } + + while (i < gWPNum) + { + if (gWPArray[i] && + gWPArray[i]->inuse && + (gWPArray[i]->flags & WPFLAG_GOALPOINT) && + gWPArray[i]->weight > highestweight && + !BotHasAssociated(bs, gWPArray[i])) + { + traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs); + + if (traildist != -1) + { + dist_to_weight = (int)traildist/10000; + dist_to_weight = (gWPArray[i]->weight)-dist_to_weight; + + if (dist_to_weight > highestweight) + { + highestweight = dist_to_weight; + desiredindex = i; + } + } + } + + i++; + } + + return desiredindex; +} + +void GetIdealDestination(bot_state_t *bs) +{ + int tempInt, cWPIndex, bChicken, idleWP; + float distChange, plusLen, minusLen; + vec3_t usethisvec, a; + gentity_t *badthing; + + if (!bs->wpCurrent) + { + return; + } + + if ((level.time - bs->escapeDirTime) > 4000) + { + badthing = GetNearestBadThing(bs); + } + else + { + badthing = NULL; + } + + if (badthing && badthing->inuse && + badthing->health > 0 && badthing->takedamage) + { + bs->dangerousObject = badthing; + } + else + { + bs->dangerousObject = NULL; + } + + if (!badthing && bs->wpDestIgnoreTime > level.time) + { + return; + } + + if (!badthing && bs->dontGoBack > level.time) + { + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + return; + } + else if (!badthing && bs->wpStoreDest) + { //after we finish running away, switch back to our original destination + bs->wpDestination = bs->wpStoreDest; + bs->wpStoreDest = NULL; + } + + if (badthing && bs->wpCamping) + { + bs->wpCamping = NULL; + } + + if (bs->wpCamping) + { + bs->wpDestination = bs->wpCamping; + return; + } + + if (!badthing && CTFTakesPriority(bs)) + { + if (bs->ctfState) + { + bs->runningToEscapeThreat = 1; + } + return; + } + + if (badthing) + { + bs->runningLikeASissy = level.time + 100; + + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + + if (bs->wpDirection) + { + tempInt = bs->wpCurrent->index+1; + } + else + { + tempInt = bs->wpCurrent->index-1; + } + + if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time) + { + VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a); + plusLen = VectorLength(a); + VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a); + minusLen = VectorLength(a); + + if (plusLen < minusLen) + { + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + + bs->wpCurrent = gWPArray[tempInt]; + + bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400); + + //G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname); + } + } + //G_Printf("Run away run away run away!\n"); + return; + } + + distChange = 0; //keep the compiler from complaining + + tempInt = BotGetWeaponRange(bs); + + if (tempInt == BWEAPONRANGE_MELEE) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_SABER) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_MID) + { + distChange = 128; + } + else if (tempInt == BWEAPONRANGE_LONG) + { + distChange = 300; + } + + if (bs->revengeEnemy && bs->revengeEnemy->health > 0 && + bs->revengeEnemy->client && (bs->revengeEnemy->client->pers.connected == CA_ACTIVE || bs->revengeEnemy->client->pers.connected == CA_AUTHORIZING)) + { //if we hate someone, always try to get to them + if (bs->wpDestSwitchTime < level.time) + { + if (bs->revengeEnemy->client) + { + VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->revengeEnemy->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->squadLeader && bs->squadLeader->health > 0 && + bs->squadLeader->client && (bs->squadLeader->client->pers.connected == CA_ACTIVE || bs->squadLeader->client->pers.connected == CA_AUTHORIZING)) + { + if (bs->wpDestSwitchTime < level.time) + { + if (bs->squadLeader->client) + { + VectorCopy(bs->squadLeader->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->squadLeader->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + bChicken = BotIsAChickenWuss(bs); + bs->runningToEscapeThreat = bChicken; + + if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2)) + { + cWPIndex = bs->wpCurrent->index; + + if (bs->frame_Enemy_Len > 400) + { //good distance away, start running toward a good place for an item or powerup or whatever + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } + else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse && + gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse) + { + VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a); + plusLen = VectorLength(a); + VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a); + minusLen = VectorLength(a); + + if (minusLen > plusLen) + { + bs->wpDestination = gWPArray[cWPIndex-1]; + } + else + { + bs->wpDestination = gWPArray[cWPIndex+1]; + } + } + } + else if (bChicken != 2 && bs->wpDestSwitchTime < level.time) + { + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + } + + if (!bs->wpDestination && bs->wpDestSwitchTime < level.time) + { + //G_Printf("I need something to do\n"); + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } +} + +void CommanderBotCTFAI(bot_state_t *bs) +{ +#ifdef BOT_KNOW_CTF + int i = 0; + gentity_t *ent; + int squadmates = 0; + gentity_t *squad[MAX_CLIENTS]; + int defendAttackPriority = 0; //0 == attack, 1 == defend + int guardDefendPriority = 0; //0 == defend, 1 == guard + int attackRetrievePriority = 0; //0 == retrieve, 1 == attack + int myFlag = 0; + int enemyFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client) + { + squad[squadmates] = ent; + squadmates++; + } + + i++; + } + + squad[squadmates] = &g_entities[bs->client]; + squadmates++; + + i = 0; + + if (enemyHasOurFlag && !weHaveEnemyFlag) + { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first. + //after that we focus on getting our flag back. + attackRetrievePriority = 1; + } + + while (i < squadmates) + { + if (squad[i] && squad[i]->client && botstates[squad[i]->s.number]) + { + if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME) + { //never tell a bot to stop trying to bring the flag to the base + if (defendAttackPriority) + { + if (weHaveEnemyFlag) + { + if (guardDefendPriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER; + guardDefendPriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + guardDefendPriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + } + defendAttackPriority = 0; + } + else + { + if (enemyHasOurFlag) + { + if (attackRetrievePriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + attackRetrievePriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + attackRetrievePriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + } + defendAttackPriority = 1; + } + } + else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + } + } + + i++; + } +#endif +} + +void BotDoTeamplayAI(bot_state_t *bs) +{ + if (bs->state_Forced) + { + bs->teamplayState = bs->state_Forced; + } + + if (bs->teamplayState == TEAMPLAYSTATE_REGROUP) + { //force to find a new leader + bs->squadLeader = NULL; + bs->isSquadLeader = 0; + } +} + +void CommanderBotTeamplayAI(bot_state_t *bs) +{ + int i = 0; + int squadmates = 0; + int teammates = 0; + int teammate_indanger = -1; + int teammate_helped = 0; + int foundsquadleader = 0; + int worsthealth = 50; + gentity_t *squad[MAX_CLIENTS]; + gentity_t *ent; + bot_state_t *bst; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number]) + { + bst = botstates[ent->s.number]; + + if (foundsquadleader && bst && bst->isSquadLeader) + { //never more than one squad leader + bst->isSquadLeader = 0; + } + + if (bst && !bst->isSquadLeader) + { + squad[squadmates] = ent; + squadmates++; + } + else if (bst) + { + foundsquadleader = 1; + } + } + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent)) + { + teammates++; + + if (ent->health < worsthealth) + { + teammate_indanger = ent->s.number; + worsthealth = ent->health; + } + } + + i++; + } + + if (!squadmates) + { + return; + } + + i = 0; + + while (i < squadmates && squad[i]) + { + bst = botstates[squad[i]->s.number]; + + if (bst && !bst->state_Forced) + { //only order if this guy is not being ordered directly by the real player team leader + if (teammate_indanger >= 0 && !teammate_helped) + { //send someone out to help whoever needs help most at the moment + bst->teamplayState = TEAMPLAYSTATE_ASSISTING; + bst->squadLeader = &g_entities[teammate_indanger]; + teammate_helped = 1; + } + else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING) + { //no teammates need help badly, but this guy is trying to help them anyway, so stop + bst->teamplayState = TEAMPLAYSTATE_FOLLOWING; + bst->squadLeader = &g_entities[bs->client]; + } + + if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5) + { //every so often tell the squad to regroup for the sake of variation + if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING) + { + bst->teamplayState = TEAMPLAYSTATE_REGROUP; + } + + bs->isSquadLeader = 0; + bs->squadCannotLead = level.time + 500; + bs->squadRegroupInterval = level.time + Q_irand(45000, 65000); + } + } + + i++; + } +} + +void CommanderBotAI(bot_state_t *bs) +{ +/* + if (level.gametype == GT_CTF) + { + CommanderBotCTFAI(bs); + } + else if (level.gametype == GT_TDM) + { + CommanderBotTeamplayAI(bs); + } +*/ +} + + +void MeleeCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + VectorCopy(usethisvec, bs->goalPosition); + } +} + +#if 0 +void SaberCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + else + { + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + } + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + if (usethisvec[2] > (bs->origin[2]+32) && + bs->currentEnemy->client && + bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + bs->jumpTime = level.time + 100; + } + + if (bs->frame_Enemy_Len > 128) + { //be ready to attack + bs->saberDefending = 0; + bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000); + } + else + { + if (bs->saberDefendDecideTime < level.time) + { + if (bs->saberDefending) + { + bs->saberDefending = 0; + } + else + { + bs->saberDefending = 1; + } + + bs->saberDefendDecideTime = level.time + Q_irand(500, 2000); + } + } + + if (bs->frame_Enemy_Len < 54) + { + VectorCopy(bs->origin, bs->goalPosition); + bs->saberBFTime = 0; + } + else + { + VectorCopy(usethisvec, bs->goalPosition); + } + + if (bs->frame_Enemy_Len > 90 && bs->saberBFTime > level.time && bs->saberBTime > level.time && bs->beStill < level.time && bs->saberSTime < level.time) + { + bs->beStill = level.time + Q_irand(500, 1000); + bs->saberSTime = level.time + Q_irand(1200, 1800); + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len < 80 && (Q_irand(1, 10) < 8 && bs->saberBFTime < level.time) || bs->saberBTime > level.time) + { + vec3_t vs; + vec3_t groundcheck; + + VectorSubtract(bs->origin, usethisvec, vs); + VectorNormalize(vs); + + bs->goalPosition[0] = bs->origin[0] + vs[0]*64; + bs->goalPosition[1] = bs->origin[1] + vs[1]*64; + bs->goalPosition[2] = bs->origin[2] + vs[2]*64; + + if (bs->saberBTime < level.time) + { + bs->saberBFTime = level.time + Q_irand(900, 1300); + bs->saberBTime = level.time + Q_irand(300, 700); + } + + VectorCopy(bs->goalPosition, groundcheck); + + groundcheck[2] -= 64; + + trap_Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID); + + if (tr.fraction == 1.0) + { //don't back off of a ledge + VectorCopy(usethisvec, bs->goalPosition); + } + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75) + { + bs->saberBFTime = level.time + Q_irand(700, 1300); + bs->saberBTime = 0; + } + + /*AngleVectors(bs->viewangles, NULL, fwd, NULL); + + if (bs->meleeStrafeDir) + { + bs->goalPosition[0] += fwd[0]*16; + bs->goalPosition[1] += fwd[1]*16; + bs->goalPosition[2] += fwd[2]*16; + } + else + { + bs->goalPosition[0] -= fwd[0]*16; + bs->goalPosition[1] -= fwd[1]*16; + bs->goalPosition[2] -= fwd[2]*16; + }*/ + } + else if (bs->frame_Enemy_Len <= 56) + { + bs->doAttack = 1; + bs->saberDefending = 0; + } +} +#endif + +float BotWeaponCanLead(bot_state_t *bs) +{ + switch (bs->cur_ps.weapon) + { + default: + return 0; + case WP_KNIFE: + return 0.5; + + // no leading needed for any bullet weapons + case WP_M1911A1_PISTOL: + case WP_USSOCOM_PISTOL: + case WP_M4_ASSAULT_RIFLE: + case WP_AK74_ASSAULT_RIFLE: + case WP_M60_MACHINEGUN: + case WP_MICRO_UZI_SUBMACHINEGUN: + case WP_M3A1_SUBMACHINEGUN: + case WP_MSG90A1: + case WP_USAS_12_SHOTGUN: + case WP_M590_SHOTGUN: + return 0; + // projectile weapons lead + case WP_MM1_GRENADE_LAUNCHER: + return 0.5; + case WP_RPG7_LAUNCHER: + return 0.5; + case WP_M67_GRENADE: + case WP_M84_GRENADE: + case WP_F1_GRENADE: + case WP_L2A2_GRENADE: + case WP_MDN11_GRENADE: + case WP_SMOHG92_GRENADE: + case WP_ANM14_GRENADE: + case WP_M15_GRENADE: + return 0.7; + } +} + +// Calculate proper angle for ballistic weapon +static float CalcWeaponAngle(float vel, float gravity, float targetRange) +{ + float angle = 0; + float val = (gravity * targetRange) / (vel * vel); + + if (val >= -1 && val <= 1) + angle = RAD2DEG(asin(val)/2); + else + angle = 90; + + return angle; +} + +void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount) +{ + int x; + vec3_t predictedSpot; + vec3_t movementVector; + vec3_t a, ang; + float vtotal; + + if (!bs->currentEnemy || + !bs->currentEnemy->client) + { + return; + } + + if (!bs->frame_Enemy_Len) + { + return; + } + + vtotal = 0; + + if (bs->currentEnemy->client->ps.velocity[0] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[0]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[0]; + } + + if (bs->currentEnemy->client->ps.velocity[1] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[1]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[1]; + } + + if (bs->currentEnemy->client->ps.velocity[2] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[2]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[2]; + } + + //G_Printf("Leadin target with a velocity total of %f\n", vtotal); + + VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector); + + VectorNormalize(movementVector); + + x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works + + if (vtotal > 400) + { + vtotal = 400; + } + + if (vtotal) + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works + } + else + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works + } + + predictedSpot[0] = headlevel[0] + (movementVector[0]*x); + predictedSpot[1] = headlevel[1] + (movementVector[1]*x); + predictedSpot[2] = headlevel[2] + (movementVector[2]*x); + + VectorSubtract(predictedSpot, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); +} + +void BotAimOffsetGoalAngles(bot_state_t *bs) +{ + int i; + float accVal; + i = 0; + + if (bs->skills.perfectaim) + { + return; + } + + if (bs->aimOffsetTime > level.time) + { + if (bs->aimOffsetAmtYaw) + { + bs->goalAngles[YAW] += bs->aimOffsetAmtYaw; + } + + if (bs->aimOffsetAmtPitch) + { + bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch; + } + + while (i <= 2) + { + if (bs->goalAngles[i] > 360) + { + bs->goalAngles[i] -= 360; + } + + if (bs->goalAngles[i] < 0) + { + bs->goalAngles[i] += 360; + } + + i++; + } + return; + } + + accVal = bs->skills.accuracy/bs->settings.skill; + + if (bs->revengeEnemy && bs->revengeHateLevel && + bs->currentEnemy == bs->revengeEnemy) + { //bot becomes more skilled as anger level raises + accVal = accVal/bs->revengeHateLevel; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { //assume our goal is aiming at the enemy, seeing as he's visible and all + if (!bs->currentEnemy->s.pos.trDelta[0] && + !bs->currentEnemy->s.pos.trDelta[1] && + !bs->currentEnemy->s.pos.trDelta[2]) + { + accVal = 0; //he's not even moving, so he shouldn't really be hard to hit. + } + else + { + accVal += accVal*0.25; //if he's moving he's this much harder to hit + } + + if (g_entities[bs->client].s.pos.trDelta[0] || + g_entities[bs->client].s.pos.trDelta[1] || + g_entities[bs->client].s.pos.trDelta[2]) + { + accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also + } + } + + if (accVal > 90) + { + accVal = 90; + } + if (accVal < 1) + { + accVal = 0; + } + + if (!accVal) + { + bs->aimOffsetAmtYaw = 0; + bs->aimOffsetAmtPitch = 0; + return; + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtYaw = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtYaw = -(rand()%(int)accVal); + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtPitch = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtPitch = -(rand()%(int)accVal); + } + + bs->aimOffsetTime = level.time + rand()%500 + 200; +} + +int ShouldSecondaryFire(bot_state_t *bs, vec3_t eorg, vec3_t dir) +{ + weaponData_t *weapon = &weaponData[bs->cur_ps.weapon]; + attackData_t *attack = &weapon->attack[ATTACK_ALTERNATE]; + + if ((bs->cur_ps.ammo[attack->ammoIndex] < 1) || 0 == attack->damage) + { // no ammo for alt fire + return 0; + } + +#ifdef BOT_LAUNCH_ANGLES + if ((attack->weaponFlags & PROJECTILE_FIRE) && bs->frame_Enemy_Len > MAX_PROJECTILE_DISTANCE) //don't forget to make sure the bot doesn't shoot it off in his own face! + { + // if alt-fire is projectile + if (bs->frame_Enemy_Len < attack->rV.velocity * attack->projectileLifetime * 0.001) + { + // in range + // 2D range + float range2D = max(0, SQRTFAST(dir[0]*dir[0] + dir[1]*dir[1]) - attack->splashRadius); + if (attack->weaponFlags & PROJECTILE_TIMED) + { // if timed projectile, let bounce for 1 sec. + range2D = max(0, range2D - attack->rV.velocity); + } + // calculate projectile launch angle + bs->launchAngle = CalcWeaponAngle( attack->rV.velocity, DEFAULT_GRAVITY, range2D); + if (bs->launchAngle < 90) + { + if (!OrgVisible(bs->eye, eorg, bs->client)) + bs->launchAngle = 90 - bs->launchAngle; + return 1; + } + } + } + else if (bs->frame_Enemy_Len < attack->rV.range) + { // alt-fire is bullet + return 1; + } +#else + if ((attack->weaponFlags & PROJECTILE_FIRE) && bs->frame_Enemy_Len > MAX_PROJECTILE_DISTANCE) //don't forget to make sure the bot doesn't shoot it off in his own face! + { + return 1; + } + else if (bs->frame_Enemy_Len < attack->rV.range) + { // alt-fire is bullet + return 1; + } +#endif + return 0; +} + +int CombatBotAI(bot_state_t *bs, float thinktime) +{ + vec3_t eorg, a, dir; + int secFire; + float fovcheck; + + if (!bs->currentEnemy) + { + return 0; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, dir); + vectoangles(dir, a); + + if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else + { + if (bs->cur_ps.weapon == WP_MSG90A1 || bs->cur_ps.weapon == WP_RPG7_LAUNCHER) + { //be careful with the hurty weapons + fovcheck = 10; + } + else + { + fovcheck = 60; + } + + if (bs->frame_Enemy_Len < 128) + { + fovcheck *= 2; + } + + if (InFieldOfVision(bs->viewangles, fovcheck, a)) + { +#ifdef BOT_LAUNCH_ANGLES + weaponData_t *weapon = &weaponData[bs->cur_ps.weapon]; + if (CAT_GRENADE == weapon->category || WP_MM1_GRENADE_LAUNCHER == bs->cur_ps.weapon) + { + attackData_t* attack; + float range2D; + + // are we using a grenade type weapon? + if (bs->frame_Enemy_Len < weapon->attack[ATTACK_NORMAL].rV.velocity * weapon->attack[ATTACK_NORMAL].projectileLifetime * 0.001) + { + attack = &weapon->attack[ATTACK_NORMAL]; + } + else + { + attack = &weapon->attack[ATTACK_ALTERNATE]; + } + + // only 2D range since Z is for the ballistic path only + range2D = max(0, SQRTFAST(dir[0]*dir[0] + dir[1]*dir[1]) - attack->splashRadius); + if (attack->weaponFlags & PROJECTILE_TIMED) + { + // if timed projectile, let bounce for 1 sec. + range2D = max(0, range2D - attack->rV.velocity); + } + + bs->launchAngle = CalcWeaponAngle(attack->rV.velocity, DEFAULT_GRAVITY, range2D); + if (bs->launchAngle < 90) + { + if (!OrgVisible(bs->eye, eorg, bs->client)) + { + bs->launchAngle = 90 - bs->launchAngle; + } + + bs->doAttack = 1; + } + } + else +#endif + { + secFire = ShouldSecondaryFire(bs, eorg, dir); + + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT) + { + bs->altChargeTime = Q_irand(500, 1000); + } + + if (secFire == 1) + { + bs->doAltAttack = 1; + } + else if (!secFire) + { + bs->doAttack = 1; + } + + if (secFire == 2) + { //released a charge + return 1; + } + } + } + } + + return 0; +} + +int BotFallbackNavigation(bot_state_t *bs) +{ + vec3_t b_angle, fwd, trto, mins, maxs; + trace_t tr; + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + return 2; //we're busy + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + bs->goalAngles[PITCH] = 0; + bs->goalAngles[ROLL] = 0; + + VectorCopy(bs->goalAngles, b_angle); + + AngleVectors(b_angle, fwd, NULL, NULL); + + trto[0] = bs->origin[0] + fwd[0]*16; + trto[1] = bs->origin[1] + fwd[1]*16; + trto[2] = bs->origin[2] + fwd[2]*16; + + trap_Trace(&tr, bs->origin, mins, maxs, trto, -1, MASK_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(trto, bs->goalPosition); + return 1; //success! + } + else + { + bs->goalAngles[YAW] = rand()%360; + } + + return 0; +} + +int BotTryAnotherWeapon(bot_state_t *bs) +{ //out of ammo, resort to the first weapon we come across that has ammo + int i; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + bs->virtualWeapon = i; + trap_EA_SelectWeapon(bs->client, i); + //bs->cur_ps.weapon = i; + //level.clients[bs->client].ps.weapon = i; + return 1; + } + + i++; + } + + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { //should always have this.. shouldn't we? + bs->virtualWeapon = 1; + trap_EA_SelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +int BotSelectIdealWeapon(bot_state_t *bs) +{ + int i; + int bestweight = -1; + int bestweapon = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) && + (bs->botWeaponWeights[i] > bestweight) && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + bestweight = bs->botWeaponWeights[i]; + bestweapon = i; + } + + i++; + } + + if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon) + { + bs->virtualWeapon = bestweapon; + trap_EA_SelectWeapon(bs->client, bestweapon); + //bs->cur_ps.weapon = bestweapon; + //level.clients[bs->client].ps.weapon = bestweapon; + return 1; + } + + return 0; +} + +int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection) +{ //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no) + int i; + int hasit = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if ((bs->cur_ps.ammo[weaponData[i].attack[ATTACK_NORMAL].ammoIndex] > 1 || bs->cur_ps.clip[ATTACK_NORMAL][i] > 1) && + i == weapon && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + hasit = 1; + break; + } + + i++; + } + + if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon) + { + bs->virtualWeapon = weapon; + trap_EA_SelectWeapon(bs->client, weapon); + //bs->cur_ps.weapon = weapon; + //level.clients[bs->client].ps.weapon = weapon; + return 2; + } + + if (hasit) + { + return 1; + } + + return 0; +} + +int BotSelectMelee(bot_state_t *bs) +{ + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { + bs->virtualWeapon = 1; + trap_EA_SelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +int GetLoveLevel(bot_state_t *bs, bot_state_t *love) +{ + int i = 0; + const char *lname = NULL; + + if (!bs || !love || !g_entities[love->client].client) + { + return 0; + } + + if (!bs->lovednum) + { + return 0; + } + + trap_Cvar_Update(&bot_attachments); + + if (!bot_attachments.integer) + { + return 0; + } + + lname = g_entities[love->client].client->pers.netname; + + if (!lname) + { + return 0; + } + + while (i < bs->lovednum) + { + if (strcmp(bs->loved[i].name, lname) == 0) + { + return bs->loved[i].level; + } + + i++; + } + + return 0; +} + +void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel) +{ + if (!loved->lastHurt || !loved->lastHurt->client || + loved->lastHurt->s.number == loved->client) + { + return; + } + + if (!IsTeamplay()) + { + if (lovelevel < 2) + { + return; + } + } + else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt)) + { //don't hate teammates no matter what + return; + } + + if (loved->client == loved->lastHurt->s.number) + { + return; + } + + if (bs->client == loved->lastHurt->s.number) + { //oops! + return; + } + + trap_Cvar_Update(&bot_attachments); + + if (!bot_attachments.integer) + { + return; + } + + if (!PassLovedOneCheck(bs, loved->lastHurt)) + { //a loved one killed a loved one.. you cannot hate them + bs->chatObject = loved->lastHurt; + bs->chatAltObject = &g_entities[loved->client]; + BotDoChat(bs, "LovedOneKilledLovedOne", 0); + return; + } + + if (bs->revengeEnemy == loved->lastHurt) + { + if (bs->revengeHateLevel < bs->loved_death_thresh) + { + bs->revengeHateLevel++; + + if (bs->revengeHateLevel == bs->loved_death_thresh) + { + //broke into the highest anger level + //CHAT: Hatred section + bs->chatObject = loved->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Hatred", 1); + } + } + } + else if (bs->revengeHateLevel < bs->loved_death_thresh-1) + { //only switch hatred if we don't hate the existing revenge-enemy too much + //CHAT: BelovedKilled section + bs->chatObject = &g_entities[loved->client]; + bs->chatAltObject = loved->lastHurt; + BotDoChat(bs, "BelovedKilled", 0); + bs->revengeHateLevel = 0; + bs->revengeEnemy = loved->lastHurt; + } +} + +void BotDeathNotify(bot_state_t *bs) +{ //in case someone has an emotional attachment to us, we'll notify them + int i = 0; + int ltest = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && botstates[i]->lovednum) + { + ltest = 0; + while (ltest < botstates[i]->lovednum) + { + if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0) + { + BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level); + break; + } + + ltest++; + } + } + + i++; + } +} + +void StrafeTracing(bot_state_t *bs) +{ + vec3_t mins, maxs; + vec3_t right, rorg, drorg; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + //mins[2] = -24; + mins[2] = -22; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + AngleVectors(bs->viewangles, NULL, right, NULL); + + if (bs->meleeStrafeDir) + { + rorg[0] = bs->origin[0] - right[0]*32; + rorg[1] = bs->origin[1] - right[1]*32; + rorg[2] = bs->origin[2] - right[2]*32; + } + else + { + rorg[0] = bs->origin[0] + right[0]*32; + rorg[1] = bs->origin[1] + right[1]*32; + rorg[2] = bs->origin[2] + right[2]*32; + } + + trap_Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } + + VectorCopy(rorg, drorg); + + drorg[2] -= 32; + + trap_Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID); + + if (tr.fraction == 1) + { //this may be a dangerous ledge, so don't strafe over it just in case + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } +} + +int PrimFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + return 1; + } + + return 0; +} + +int KeepPrimFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + bs->doAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + bs->doAttack = 1; + } + + return 0; +} + +int AltFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + return 1; + } + + return 0; +} + +int KeepAltFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + bs->doAltAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + bs->doAltAttack = 1; + } + + return 0; +} + +gentity_t *CheckForFriendInLOF(bot_state_t *bs) +{ + vec3_t fwd; + vec3_t trfrom, trto; + vec3_t mins, maxs; + gentity_t *trent; + trace_t tr; + + mins[0] = -3; + mins[1] = -3; + mins[2] = -3; + + maxs[0] = 3; + maxs[1] = 3; + maxs[2] = 3; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + VectorCopy(bs->eye, trfrom); + + trto[0] = trfrom[0] + fwd[0]*2048; + trto[1] = trfrom[1] + fwd[1]*2048; + trto[2] = trfrom[2] + fwd[2]*2048; + + trap_Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum <= MAX_CLIENTS) + { + trent = &g_entities[tr.entityNum]; + + if (trent && trent->client) + { + if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent)) + { + return trent; + } + + if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1) + { + return trent; + } + } + } + + return NULL; +} + +void BotScanForLeader(bot_state_t *bs) +{ //bots will only automatically obtain a leader if it's another bot using this method. + int i = 0; + gentity_t *ent; + + if (bs->isSquadLeader) + { + return; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i) + { + if (OnSameTeam(&g_entities[bs->client], ent)) + { + bs->squadLeader = ent; + break; + } + if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay()) + { //ignore love status regarding squad leaders if we're in teamplay + bs->squadLeader = ent; + break; + } + } + + i++; + } +} + +void BotReplyGreetings(bot_state_t *bs) +{ + int i = 0; + int numhello = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->canChat && + i != bs->client) + { + botstates[i]->chatObject = &g_entities[bs->client]; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "ResponseGreetings", 0)) + { + numhello++; + } + } + + if (numhello > 3) + { //don't let more than 4 bots say hello at once + return; + } + + i++; + } +} + +void CTFFlagMovement(bot_state_t *bs) +{ + int diddrop = 0; + gentity_t *desiredDrop = NULL; + vec3_t a, mins, maxs; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -7; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 7; + + if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM)) + { + if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] && + bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] && + bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2]) + { + VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition); + return; + } + else + { + bs->wantFlag = NULL; + } + } + else + { + bs->wantFlag = NULL; + } + } + else if (bs->wantFlag) + { + bs->wantFlag = NULL; + } + + if (flagRed && flagBlue) + { + if (bs->wpDestination == flagRed || + bs->wpDestination == flagBlue) + { + if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0) + { + desiredDrop = droppedRedFlag; + diddrop = 1; + } + if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0) + { + desiredDrop = droppedBlueFlag; + diddrop = 1; + } + + if (diddrop && desiredDrop) + { + VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + trap_Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number) + { + VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition); + VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot); + return; + } + } + } + } + } +} + +int BotUseInventoryItem(bot_state_t *bs) +{ +#ifdef BOT_USE_HOLDABLE + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) + { + if (g_entities[bs->client].health <= 50) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat) + { //this will (hopefully) result in the bot placing the shield down while facing + //the enemy and running away + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE); + goto wantuseitem; + } + } + + return 0; + +wantuseitem: + level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM]; + + return 1; +#else + return 0; +#endif +} + +int BotSurfaceNear(bot_state_t *bs) +{ + trace_t tr; + vec3_t fwd; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + fwd[0] = bs->origin[0]+(fwd[0]*64); + fwd[1] = bs->origin[1]+(fwd[1]*64); + fwd[2] = bs->origin[2]+(fwd[2]*64); + + trap_Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +wpobject_t *GetLastWP(bot_state_t *bs, int wpIndex) +{ + int goalWPIndex = 0; + + //reverse because we want the last wp to the current, not the next + if (bs->wpDirection) + { + goalWPIndex = bs->wpCurrent->index+1; + } + else + { + goalWPIndex = bs->wpCurrent->index-1; + } + + if (goalWPIndex < 0) + { + return NULL; + } + + if (goalWPIndex >= gWPNum) + { + return NULL; + } + + return gWPArray[goalWPIndex]; +} + +void StandardBotAI(bot_state_t *bs, float thinktime) +{ + int wp, enemy; + int desiredIndex; + int goalWPIndex; + int doingFallback; + vec3_t a, ang, headlevel, eorg, dif; + float reaction; + float bLeadAmount; + int meleestrafe = 0; + int cBAI = 0; + gentity_t *friendInLOF = 0; + int visResult = 0; + int selResult = 0; + + trap_Cvar_Update(&bot_pause); + + if (gDeactivated || bot_pause.integer ) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + if (g_entities[bs->client].inuse && + g_entities[bs->client].client && + G_IsClientSpectating ( g_entities[bs->client].client ) ) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + + if (!bs->lastDeadTime) + { //just spawned in? + bs->lastDeadTime = level.time; + } + + if (g_entities[bs->client].health < 1) + { + bs->lastDeadTime = level.time; + + if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client) + { + BotDeathNotify(bs); + if (PassLovedOneCheck(bs, bs->lastHurt)) + { + //CHAT: Died + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Died", 0); + } + else if (!PassLovedOneCheck(bs, bs->lastHurt) && + botstates[bs->lastHurt->s.number] && + PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client])) + { //killed by a bot that I love, but that does not love me + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledOnPurposeByLove", 0); + } + + bs->deathActivitiesDone = 1; + } + + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpCamping = NULL; + bs->wpCampingTo = NULL; + bs->wpStoreDest = NULL; + bs->wpDestIgnoreTime = 0; + bs->wpDestSwitchTime = 0; + bs->wpSeenTime = 0; + bs->wpDirection = 0; + + if (rand()%10 < 5 && + (!bs->doChat || bs->chatTime < level.time)) + { + trap_EA_Attack(bs->client); + } + + return; + } + + bs->doAttack = 0; + bs->doAltAttack = 0; + //reset the attack states + + if (bs->isSquadLeader) + { + CommanderBotAI(bs); + } + else + { + BotDoTeamplayAI(bs); + } + + if (!bs->currentEnemy) + { + bs->frame_Enemy_Vis = 0; + } + + if (bs->revengeEnemy && bs->revengeEnemy->client && + bs->revengeEnemy->client->pers.connected != CA_ACTIVE && bs->revengeEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->pers.connected != CA_ACTIVE && bs->currentEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->currentEnemy = NULL; + } + + doingFallback = 0; + + bs->deathActivitiesDone = 0; + + if (BotUseInventoryItem(bs)) + { + if (rand()%10 < 5) + { + trap_EA_Use(bs->client); + } + } + + if ((bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].attack[ATTACK_NORMAL].ammoIndex] < 1 && bs->cur_ps.clip[ATTACK_NORMAL][bs->cur_ps.weapon] < 1)) + { + if (BotTryAnotherWeapon(bs)) + { + return; + } + } + else + { + if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && + bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/) + { + bs->forceWeaponSelect = 0; + } + + if (bs->forceWeaponSelect) + { + selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1); + } + + if (selResult) + { + if (selResult == 2) + { //newly selected + return; + } + } + else if (BotSelectIdealWeapon(bs)) + { + return; + } + } + /*if (BotSelectMelee(bs)) + { + return; + }*/ + + reaction = bs->skills.reflex/bs->settings.skill; + + if (reaction < 0) + { + reaction = 0; + } + if (reaction > 2000) + { + reaction = 2000; + } + + if (!bs->currentEnemy) + { + bs->timeToReact = level.time + reaction; + } + + if (bs->wpCamping) + { + if (bs->isCamping < level.time) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + } + + if (bs->wpCurrent && + (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time)) + { + bs->wpCurrent = NULL; + } + + if (bs->currentEnemy) + { + if (bs->enemySeenTime < level.time || + !PassStandardEnemyChecks(bs, bs->currentEnemy)) + { + if (bs->revengeEnemy == bs->currentEnemy && + bs->currentEnemy->health < 1 && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Destroyed hated one [KilledHatedOne section] + bs->chatObject = bs->revengeEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledHatedOne", 1); + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Killed + bs->chatObject = bs->currentEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "Killed", 0); + } + + bs->currentEnemy = NULL; + } + } + + if (!bs->wpCurrent) + { + wp = GetNearestVisibleWP(bs->origin, bs->client); + + if (wp != -1) + { + bs->wpCurrent = gWPArray[wp]; + bs->wpSeenTime = level.time + 1500; + bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint + } + } + + if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy || + (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/)) + { + enemy = ScanForEnemies(bs); + + if (enemy != -1) + { + bs->currentEnemy = &g_entities[enemy]; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + } + + if (!bs->squadLeader && !bs->isSquadLeader) + { + BotScanForLeader(bs); + } + + if (!bs->squadLeader && bs->squadCannotLead < level.time) + { //if still no leader after scanning, then become a squad leader + bs->isSquadLeader = 1; + } + + if (bs->isSquadLeader && bs->squadLeader) + { //we don't follow anyone if we are a leader + bs->squadLeader = NULL; + } + + //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE + if (bs->wpCurrent) + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + bs->frame_Waypoint_Len = VectorLength(a); + + visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client); + + if (visResult == 2) + { + bs->frame_Waypoint_Vis = 0; + bs->wpSeenTime = 0; + bs->wpDestination = NULL; + bs->wpDestIgnoreTime = level.time + 5000; + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + else if (visResult) + { + bs->frame_Waypoint_Vis = 1; + } + else + { + bs->frame_Waypoint_Vis = 0; + } + } + + if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + eorg[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, a); + bs->frame_Enemy_Len = VectorLength(a); + + if (OrgVisible(bs->eye, eorg, bs->client)) + { + bs->frame_Enemy_Vis = 1; + VectorCopy(eorg, bs->lastEnemySpotted); + VectorCopy(bs->origin, bs->hereWhenSpotted); + bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number; + //VectorCopy(bs->eye, bs->lastEnemySpotted); + bs->hitSpotted = 0; + } + else + { + bs->frame_Enemy_Vis = 0; + } + } + else + { + bs->lastVisibleEnemyIndex = ENTITYNUM_NONE; + } + //END + + if (bs->frame_Enemy_Vis) + { + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + + if (bs->wpCurrent) + { + WPConstantRoutine(bs); + + if (!bs->wpCurrent) + { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so.. + return; + } + + if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC) + { + if (!CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //no func brush under.. wait + } + } + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { + if (CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //func brush under.. wait + } + } + + if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS)) + { + bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it + } + VectorCopy(bs->wpCurrent->origin, bs->goalPosition); + if (bs->wpDirection) + { + goalWPIndex = bs->wpCurrent->index-1; + } + else + { + goalWPIndex = bs->wpCurrent->index+1; + } + + if (bs->wpCamping) + { + VectorSubtract(bs->wpCampingTo->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + VectorSubtract(bs->origin, bs->wpCamping->origin, a); + if (VectorLength(a) < 64) + { + VectorCopy(bs->wpCamping->origin, bs->goalPosition); + bs->beStill = level.time + 1000; + + if (!bs->campStanding) + { + bs->duckTime = level.time + 1000; + } + } + } + /* + else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse && + !(bs->cur_ps.pm_flags & PMF_LADDER)) + { //don't look at one ahead if on a ladder.. + if (bs->cur_ps.pm_flags & PMF_LADDER) + { + vec3_t ladderMod; + wpobject_t *prevWP; + + prevWP = GetLastWP(bs, gWPArray[goalWPIndex]->index); + + VectorCopy(gWPArray[goalWPIndex]->origin, ladderMod); + + if (prevWP && prevWP->origin[2] < gWPArray[goalWPIndex]->origin[2]) + { //We're going up a ladder, as opposed to down + ladderMod[2] += 400; + } + VectorSubtract(ladderMod, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + //bs->jumpTime = level.time + 100; + } + else + { + VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + } + */ + //This code usually causes horrible issues in SoF2 because the levels are more cramped and tight. + //So I guess for now they'll just have snappier viewangles. + else + { + if (bs->cur_ps.pm_flags & PMF_LADDER) + { + vec3_t ladderMod; + wpobject_t *prevWP; + + prevWP = GetLastWP(bs, bs->wpCurrent->index); + + VectorCopy(bs->wpCurrent->origin, ladderMod); + + if (prevWP && prevWP->origin[2] < bs->wpCurrent->origin[2]) + { //We're going up a ladder, as opposed to down + ladderMod[2] += 400; + } + VectorSubtract(ladderMod, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + //bs->jumpTime = level.time + 100; + } + else + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + } + + if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/) + { + GetIdealDestination(bs); + } + + if (bs->wpCurrent && bs->wpDestination) + { + if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + } + + if (bs->frame_Waypoint_Len < BOT_WPTOUCH_DISTANCE) + { + WPTouchRoutine(bs); + + if (!bs->wpDirection) + { + desiredIndex = bs->wpCurrent->index+1; + } + else + { + desiredIndex = bs->wpCurrent->index-1; + } + + if (gWPArray[desiredIndex] && + gWPArray[desiredIndex]->inuse && + desiredIndex < gWPNum && + desiredIndex >= 0 && + PassWayCheck(bs, desiredIndex)) + { + bs->wpCurrent = gWPArray[desiredIndex]; + } + else + { + if (bs->wpDestination) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + } + } + else //We can't find a waypoint, going to need a fallback routine. + { + doingFallback = BotFallbackNavigation(bs); + } + + if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2))) + { + if (bs->frame_Enemy_Vis) + { + cBAI = CombatBotAI(bs, thinktime); + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { //keep charging in case we see him again before we lose track of him + bs->doAltAttack = 1; + } + + if (bs->destinationGrabTime > level.time + 100) + { + bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation + } + + if (bs->cur_ps.pm_flags & PMF_LADDER) + { //keep navigating the ladder but if anyone happens in front of us on it, shoot them up good. + goto skipviewchecks; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + headlevel[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + } + + bLeadAmount = BotWeaponCanLead(bs); + if ((bs->skills.accuracy/bs->settings.skill) <= 8 && + bLeadAmount) + { + BotAimLeading(bs, headlevel, bLeadAmount); + } + else + { + VectorSubtract(headlevel, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + +#ifdef BOT_LAUNCH_ANGLES + if ((CAT_GRENADE == weapon->category || + WP_MM1_GRENADE_LAUNCHER == bs->cur_ps.weapon) && bs->launchAngle < 90) + { + bs->goalAngles[PITCH] = bs->goalAngles[PITCH]-bs->launchAngle; + } +#endif + + BotAimOffsetGoalAngles(bs); + } +skipviewchecks: + if (bs->currentEnemy) + { + if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + MeleeCombatHandling(bs); + meleestrafe = 1; + } + } + } + + if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are + { + VectorCopy(bs->origin, bs->goalPosition); + } + + if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis)) + { + return; + } + else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis) + { + //bs->chatTime = level.time + bs->chatTime_stored; + bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone? + bs->chatTeam = 0; + } + else if (bs->doChat && bs->chatTime <= level.time) + { + if (bs->chatTeam) + { + trap_EA_SayTeam(bs->client, bs->currentChat); + bs->chatTeam = 0; + } + else + { + trap_EA_Say(bs->client, bs->currentChat); + } + if (bs->doChat == 2) + { + BotReplyGreetings(bs); + } + bs->doChat = 0; + } + + CTFFlagMovement(bs); + + if (/*bs->wpDestination &&*/ bs->shootGoal && + /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/ + bs->shootGoal->health > 0 && bs->shootGoal->takedamage) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!bs->currentEnemy || bs->frame_Enemy_Len > 256) + { //if someone is close then don't stop shooting them for this + VectorSubtract(dif, bs->eye, a); + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition)) + { + VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir); + VectorNormalize(bs->goalMovedir); + + if (bs->jumpTime > level.time && bs->jDelay < level.time && + level.clients[bs->client].pers.cmd.upmove > 0) + { + // trap_EA_Move(bs->client, bs->origin, 5000); + bs->beStill = level.time + 200; + } + else + { + trap_EA_Move(bs->client, bs->goalMovedir, 5000); + } + + if (meleestrafe) + { + StrafeTracing(bs); + } + + if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveRight(bs->client); + } + else if (meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveLeft(bs->client); + } + + if (BotTrace_Jump(bs, bs->goalPosition)) + { + bs->jumpTime = level.time + 100; + } + else if (BotTrace_Duck(bs, bs->goalPosition)) + { + bs->duckTime = level.time + 100; + } +#ifdef BOT_STRAFE_AVOIDANCE + else + { + int strafeAround = BotTrace_Strafe(bs, bs->goalPosition); + + if (strafeAround == STRAFEAROUND_RIGHT) + { + trap_EA_MoveRight(bs->client); + } + else if (strafeAround == STRAFEAROUND_LEFT) + { + trap_EA_MoveLeft(bs->client); + } + } +#endif + } + + if (bs->jumpTime > level.time && bs->jDelay < level.time) + { + if (!(bs->cur_ps.pm_debounce & PMD_JUMP )) + { + trap_EA_Jump(bs->client); + } + } + + if (bs->duckTime > level.time) + { + trap_EA_Crouch(bs->client); + } + + if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 && + bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) && + (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) && +// bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE && + !bs->shootGoal ) + { + float danLen; + + VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a); + + danLen = VectorLength(a); + + if (danLen > 256) + { + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (Q_irand(1, 10) < 5) + { + bs->goalAngles[YAW] += Q_irand(0, 3); + bs->goalAngles[PITCH] += Q_irand(0, 3); + } + else + { + bs->goalAngles[YAW] -= Q_irand(0, 3); + bs->goalAngles[PITCH] -= Q_irand(0, 3); + } + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (PrimFiring(bs) || + AltFiring(bs)) + { + friendInLOF = CheckForFriendInLOF(bs); + + if (friendInLOF) + { + if (PrimFiring(bs)) + { + KeepPrimFromFiring(bs); + } + if (AltFiring(bs)) + { + KeepAltFromFiring(bs); + } + } + } + + if (bs->doAttack) + { + trap_EA_Attack(bs->client); + } + else if (bs->doAltAttack) + { + trap_EA_Alt_Attack(bs->client); + } + + MoveTowardIdealAngles(bs); +} +//end rww + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) +{ +#ifdef _SOF2_BOTS + int i; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + G_CheckBotSpawn(); + + //rww - addl bot frame functions + if (gBotEdit) + { + trap_Cvar_Update(&bot_wp_info); + BotWaypointRender(); + } + + UpdateEventTracker(); + //end rww + + //cap the bot think time + //if the bot think time changed we should reschedule the bots + if (BOT_THINK_TIME != lastbotthink_time) { + lastbotthink_time = BOT_THINK_TIME; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time; + else thinktime = BOT_THINK_TIME; + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (g_entities[i].client->pers.connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +#else + return qfalse; +#endif +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + //rww - new bot cvars.. +#ifdef _DEBUG + trap_Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT, 0.0, 0.0); +#endif + + trap_Cvar_Register(&bot_attachments, "bot_attachments", "1", 0, 0.0, 0.0); + trap_Cvar_Register(&bot_camp, "bot_camp", "1", 0, 0.0, 0.0); + trap_Cvar_Register(&bot_pause, "bot_pause", "0", 0, 0.0, 0.0); + + trap_Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0, 0.0, 0.0); + trap_Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT, 0.0, 0.0); + trap_Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0, 0.0, 0.0); + trap_Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0, 0.0, 0.0); + trap_Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0, 0.0, 0.0); + + //end rww + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + if (!trap_BotLibSetup()) + { + return qfalse; //wts?! + } + + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/code/game/ai_main.h b/code/game/ai_main.h new file mode 100644 index 0000000..5ce7bca --- /dev/null +++ b/code/game/ai_main.h @@ -0,0 +1,363 @@ +#define DEFAULT_FORCEPOWERS "5-1-000000000000000000" + +#define MAX_CHAT_BUFFER_SIZE 65536 +#define MAX_CHAT_LINE_SIZE 128 + +#define MAX_WPARRAY_SIZE 4096 +#define MAX_NEIGHBOR_SIZE 32 + +#define MAX_NEIGHBOR_LINK_DISTANCE 128 +#define MAX_NEIGHBOR_FORCEJUMP_LINK_DISTANCE 400 + +#define TABLE_BRANCH_DISTANCE 32 +#define MAX_NODETABLE_SIZE 4096 + +#define MAX_LOVED_ONES 4 +#define MAX_ATTACHMENT_NAME 64 + +#define MAX_FORCE_INFO_SIZE 2048 + +#define WPFLAG_JUMP 0x00000010 //jump when we hit this +#define WPFLAG_DUCK 0x00000020 //duck while moving around here +#define WPFLAG_NOVIS 0x00000400 //go here for a bit even with no visibility +#define WPFLAG_SNIPEORCAMPSTAND 0x00000800 //a good position to snipe or camp - stand +#define WPFLAG_WAITFORFUNC 0x00001000 //wait for a func brushent under this point before moving here +#define WPFLAG_SNIPEORCAMP 0x00002000 //a good position to snipe or camp - crouch +#define WPFLAG_ONEWAY_FWD 0x00004000 //can only go forward on the trial from here (e.g. went over a ledge) +#define WPFLAG_ONEWAY_BACK 0x00008000 //can only go backward on the trail from here +#define WPFLAG_GOALPOINT 0x00010000 //make it a goal to get here.. goal points will be decided by setting "weight" values +#define WPFLAG_RED_FLAG 0x00020000 //red flag +#define WPFLAG_BLUE_FLAG 0x00040000 //blue flag +#define WPFLAG_NOMOVEFUNC 0x00200000 //don't move over if a func is under + +#define WP_KEEP_FLAG_DIST 128 + +#define BWEAPONRANGE_MELEE 1 +#define BWEAPONRANGE_MID 2 +#define BWEAPONRANGE_LONG 3 +#define BWEAPONRANGE_SABER 4 + +#define MELEE_ATTACK_RANGE 256 +#define SABER_ATTACK_RANGE 128 +#define MAX_CHICKENWUSS_TIME 10000 //wait 10 secs between checking which run-away path to take + +#define BOT_RUN_HEALTH 40 +#define BOT_WPTOUCH_DISTANCE 32 +#define ENEMY_FORGET_MS 10000 +//if our enemy isn't visible within 10000ms (aprx 10sec) then "forget" about him and treat him like every other threat, but still look for +//more immediate threats while main enemy is not visible + +#define BOT_PLANT_DISTANCE 256 //plant if within this radius from the last spotted enemy position +#define BOT_PLANT_INTERVAL 15000 //only plant once per 15 seconds at max +#define BOT_PLANT_BLOW_DISTANCE 256 //blow det packs if enemy is within this radius and I am further away than the enemy + +#define BOT_MAX_WEAPON_GATHER_TIME 1000 //spend a max of 1 second after spawn issuing orders to gather weapons before attacking enemy base +#define BOT_MAX_WEAPON_CHASE_TIME 15000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) + +#define BOT_MAX_WEAPON_CHASE_CTF 5000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) [ctf-only] + +#define BOT_MIN_SAGA_GOAL_SHOOT 1024 +#define BOT_MIN_SAGA_GOAL_TRAVEL 128 + +#define BASE_GUARD_DISTANCE 256 //guarding the flag +#define BASE_FLAGWAIT_DISTANCE 256 //has the enemy flag and is waiting in his own base for his flag to be returned +#define BASE_GETENEMYFLAG_DISTANCE 256 //waiting around to get the enemy's flag + +#define BOT_FLAG_GET_DISTANCE 256 + +#define MAX_PROJECTILE_DISTANCE 256 + +typedef enum +{ + CTFSTATE_NONE, + CTFSTATE_ATTACKER, + CTFSTATE_DEFENDER, + CTFSTATE_RETRIEVAL, + CTFSTATE_GUARDCARRIER, + CTFSTATE_GETFLAGHOME, + CTFSTATE_MAXCTFSTATES +} bot_ctf_state_t; + +typedef enum +{ + SAGASTATE_NONE, + SAGASTATE_ATTACKER, + SAGASTATE_DEFENDER, + SAGASTATE_MAXSAGASTATES +} bot_saga_state_t; + +typedef enum +{ + TEAMPLAYSTATE_NONE, + TEAMPLAYSTATE_FOLLOWING, + TEAMPLAYSTATE_ASSISTING, + TEAMPLAYSTATE_REGROUP, + TEAMPLAYSTATE_MAXTPSTATES +} bot_teamplay_state_t; + +typedef struct wpneighbor_s +{ + int num; + int forceJumpTo; +} wpneighbor_t; + +typedef struct wpobject_s +{ + vec3_t origin; + int inuse; + int index; + float weight; + float disttonext; + int flags; + int associated_entity; + + int forceJumpTo; + + int neighbornum; + //int neighbors[MAX_NEIGHBOR_SIZE]; + wpneighbor_t neighbors[MAX_NEIGHBOR_SIZE]; +} wpobject_t; + +typedef struct botattachment_s +{ + int level; + char name[MAX_ATTACHMENT_NAME]; +} botattachment_t; + +typedef struct nodeobject_s +{ + vec3_t origin; + int inuse; + int index; + float weight; + int flags; + int neighbornum; +} nodeobject_t; + +typedef struct boteventtracker_s +{ + int eventSequence; + int events[MAX_PS_EVENTS]; + float eventTime; +} boteventtracker_t; + +typedef struct botskills_s +{ + int reflex; + float accuracy; + float turnspeed; + float turnspeed_combat; + float maxturn; + int perfectaim; +} botskills_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + usercmd_t lastucmd; //usercmd from last frame + bot_settings_t settings; //several bot settings + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t eye; //eye coordinates of the bot + int setupcount; //true when the bot has just been setup + float ltime; //local bot time + float entergame_time; //time the bot entered the game + int ms; //move state of the bot + int gs; //goal state of the bot + int ws; //weapon state of the bot + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + + //rww - new AI values + gentity_t *currentEnemy; + gentity_t *revengeEnemy; + + gentity_t *squadLeader; + + gentity_t *lastHurt; + gentity_t *lastAttacked; + + gentity_t *wantFlag; + + gentity_t *touchGoal; + gentity_t *shootGoal; + + gentity_t *dangerousObject; + + vec3_t staticFlagSpot; + + int revengeHateLevel; + int isSquadLeader; + + int squadRegroupInterval; + int squadCannotLead; + + int lastDeadTime; + + wpobject_t *wpCurrent; + wpobject_t *wpDestination; + wpobject_t *wpStoreDest; + vec3_t goalAngles; + vec3_t goalMovedir; + vec3_t goalPosition; + + vec3_t lastEnemySpotted; + vec3_t hereWhenSpotted; + int lastVisibleEnemyIndex; + int hitSpotted; + + int wpDirection; + + float destinationGrabTime; + float wpSeenTime; + float wpTravelTime; + float wpDestSwitchTime; + float wpSwitchTime; + float wpDestIgnoreTime; + + float timeToReact; + + float enemySeenTime; + + float chickenWussCalculationTime; + + float beStill; + float duckTime; + float jumpTime; + float jDelay; + + float aimOffsetTime; + float aimOffsetAmtYaw; + float aimOffsetAmtPitch; + + float frame_Waypoint_Len; + int frame_Waypoint_Vis; + float frame_Enemy_Len; + int frame_Enemy_Vis; + + int isCamper; + float isCamping; + wpobject_t *wpCamping; + wpobject_t *wpCampingTo; + qboolean campStanding; + + int randomNavTime; + int randomNav; + + int meleeSpecialist; + + int canChat; + int chatFrequency; + char currentChat[MAX_CHAT_LINE_SIZE]; + float chatTime; + float chatTime_stored; + int doChat; + int chatTeam; + gentity_t *chatObject; + gentity_t *chatAltObject; + + float meleeStrafeTime; + int meleeStrafeDir; + float meleeStrafeDisable; + + int altChargeTime; + + float escapeDirTime; + + float dontGoBack; + + int doAttack; + int doAltAttack; + + int forceWeaponSelect; + int virtualWeapon; + + int runningLikeASissy; + int runningToEscapeThreat; + + //char chatBuffer[MAX_CHAT_BUFFER_SIZE]; + //Since we're once again not allocating bot structs dynamically, + //shoving a 64k chat buffer into one is a bad thing. + + botskills_t skills; + + botattachment_t loved[MAX_LOVED_ONES]; + int lovednum; + + int loved_death_thresh; + + int deathActivitiesDone; + + float botWeaponWeights[WP_NUM_WEAPONS]; + + int ctfState; + + int teamplayState; + + int state_Forced; //set by player ordering menu + + int noUseTime; + + vec3_t launchAngle; + //end rww +} bot_state_t; + +void *B_TempAlloc(int size); +void B_TempFree(int size); + +void *B_Alloc(int size); +void B_Free(void *ptr); + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); + +void BotUtilizePersonality(bot_state_t *bs); +int BotDoChat(bot_state_t *bs, char *section, int always); +void StandardBotAI(bot_state_t *bs, float thinktime); +void BotWaypointRender(void); +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore); +int BotIsAChickenWuss(bot_state_t *bs); +int GetNearestVisibleWP(vec3_t org, int ignore); +int GetBestIdleGoal(bot_state_t *bs); + +char *ConcatArgs( int start ); + +extern vmCvar_t bot_attachments; +extern vmCvar_t bot_camp; + +extern vmCvar_t bot_wp_info; +extern vmCvar_t bot_wp_edit; +extern vmCvar_t bot_wp_clearweight; +extern vmCvar_t bot_wp_distconnect; +extern vmCvar_t bot_wp_visconnect; + +extern wpobject_t *flagRed; +extern wpobject_t *oFlagRed; +extern wpobject_t *flagBlue; +extern wpobject_t *oFlagBlue; + +extern gentity_t *eFlagRed; +extern gentity_t *eFlagBlue; + +extern char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; +extern float gWPRenderTime; +extern float gDeactivated; +extern float gBotEdit; +extern int gWPRenderedFrame; +extern wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +extern int gWPNum; +extern int gLastPrintedIndex; +extern nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +extern int nodenum; + +extern float floattime; +#define FloatTime() floattime diff --git a/code/game/ai_util.c b/code/game/ai_util.c new file mode 100644 index 0000000..ff46b44 --- /dev/null +++ b/code/game/ai_util.c @@ -0,0 +1,799 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// ai_util.c + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +#ifdef BOT_ZMALLOC +#define MAX_BALLOC 8192 + +void *BAllocList[MAX_BALLOC]; +#endif + +char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; +//A total of 4194304 bytes. Not very nice at all, but we really +//want to have at least 65k for the total chat buffer just in +//case and we have no method of dynamic allocation here. + +void *B_TempAlloc(int size) +{ + return trap_VM_LocalTempAlloc(size); +} + +void B_TempFree(int size) +{ + trap_VM_LocalTempFree(size); +} + + +void *B_Alloc(int size) +{ +#ifdef BOT_ZMALLOC + void *ptr = NULL; + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + Com_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + ptr = trap_BotGetMemoryGame(size); + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + BAllocList[i] = ptr; + break; + } + i++; + } + + if (i == MAX_BALLOC) + { + //If this happens we'll have to rely on this chunk being freed manually with B_Free, which it hopefully will be +#ifdef DEBUG + Com_Printf("WARNING: MAXIMUM B_ALLOC ALLOCATIONS EXCEEDED\n"); +#endif + } + + return ptr; +#else + + return trap_VM_LocalAlloc(size); + +#endif +} + +void B_Free(void *ptr) +{ +#ifdef BOT_ZMALLOC + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + Com_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + while (i < MAX_BALLOC) + { + if (BAllocList[i] == ptr) + { + BAllocList[i] = NULL; + break; + } + + i++; + } + + if (i == MAX_BALLOC) + { + //Likely because the limit was exceeded and we're now freeing the chunk manually as we hoped would happen +#ifdef DEBUG + Com_Printf("WARNING: Freeing allocation which is not in the allocation structure\n"); +#endif + } + + trap_BotFreeMemoryGame(ptr); +#endif +} + +void B_InitAlloc(void) +{ +#ifdef BOT_ZMALLOC + memset(BAllocList, 0, sizeof(BAllocList)); +#endif + + memset(gWPArray, 0, sizeof(gWPArray)); +} + +void B_CleanupAlloc(void) +{ +#ifdef BOT_ZMALLOC + int i = 0; + + while (i < MAX_BALLOC) + { + if (BAllocList[i]) + { + trap_BotFreeMemoryGame(BAllocList[i]); + BAllocList[i] = NULL; + } + + i++; + } +#endif +} + +int GetValueGroup(char *buf, char *group, char *outbuf) +{ + char *place, *placesecond; + int iplace; + int failure; + int i; + int startpoint, startletter; + int subg = 0; + + i = 0; + + iplace = 0; + + place = strstr(buf, group); + + if (!place) + { + return 0; + } + + startpoint = place - buf + strlen(group) + 1; + startletter = (place - buf) - 1; + + failure = 0; + + while (buf[startpoint+1] != '{' || buf[startletter] != '\n') + { + placesecond = strstr(place+1, group); + + if (placesecond) + { + startpoint += (placesecond - place); + startletter += (placesecond - place); + place = placesecond; + } + else + { + failure = 1; + break; + } + } + + if (failure) + { + return 0; + } + + //we have found the proper group name if we made it here, so find the opening brace and read into the outbuf + //until hitting the end brace + + while (buf[startpoint] != '{') + { + startpoint++; + } + + startpoint++; + + while (buf[startpoint] != '}' || subg) + { + if (buf[startpoint] == '{') + { + subg++; + } + else if (buf[startpoint] == '}') + { + subg--; + } + outbuf[i] = buf[startpoint]; + i++; + startpoint++; + } + outbuf[i] = '\0'; + + return 1; +} + +int GetPairedValue(char *buf, char *key, char *outbuf) +{ + char *place, *placesecond; + int startpoint, startletter; + int i, found; + + if (!buf || !key || !outbuf) + { + return 0; + } + + i = 0; + + while (buf[i] && buf[i] != '\0') + { + if (buf[i] == '/') + { + if (buf[i+1] && buf[i+1] != '\0' && buf[i+1] == '/') + { + while (buf[i] != '\n') + { + buf[i] = '/'; + i++; + } + } + } + i++; + } + + place = strstr(buf, key); + + if (!place) + { + return 0; + } + //tab == 9 + startpoint = place - buf + strlen(key); + startletter = (place - buf) - 1; + + found = 0; + + while (!found) + { + if (startletter == 0 || !buf[startletter] || buf[startletter] == '\0' || buf[startletter] == 9 || buf[startletter] == ' ' || buf[startletter] == '\n') + { + if (buf[startpoint] == '\0' || buf[startpoint] == 9 || buf[startpoint] == ' ' || buf[startpoint] == '\n') + { + found = 1; + break; + } + } + + placesecond = strstr(place+1, key); + + if (placesecond) + { + startpoint += placesecond - place; + startletter += placesecond - place; + place = placesecond; + } + else + { + place = NULL; + break; + } + + } + + if (!found || !place || !buf[startpoint] || buf[startpoint] == '\0') + { + return 0; + } + + while (buf[startpoint] == ' ' || buf[startpoint] == 9 || buf[startpoint] == '\n') + { + startpoint++; + } + + i = 0; + + while (buf[startpoint] && buf[startpoint] != '\0' && buf[startpoint] != '\n') + { + outbuf[i] = buf[startpoint]; + i++; + startpoint++; + } + + outbuf[i] = '\0'; + + return 1; +} + +int BotDoChat(bot_state_t *bs, char *section, int always) +{ + char *chatgroup; + int rVal; + int inc_1; + int inc_2; + int inc_n; + int lines; + int checkedline; + int getthisline; + gentity_t *cobject; + + if (!bs->canChat) + { + return 0; + } + + if (bs->doChat) + { //already have a chat scheduled + return 0; + } + + if (Q_irand(1, 10) > bs->chatFrequency && !always) + { + return 0; + } + + bs->chatTeam = 0; + + chatgroup = (char *)B_TempAlloc(MAX_CHAT_BUFFER_SIZE); + + rVal = GetValueGroup(gBotChatBuffer[bs->client], section, chatgroup); + + if (!rVal) //the bot has no group defined for the specified chat event + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + inc_1 = 0; + inc_2 = 2; + + while (chatgroup[inc_2] && chatgroup[inc_2] != '\0') + { + if (chatgroup[inc_2] != 13 && chatgroup[inc_2] != 9) + { + chatgroup[inc_1] = chatgroup[inc_2]; + inc_1++; + } + inc_2++; + } + chatgroup[inc_1] = '\0'; + + inc_1 = 0; + + lines = 0; + + while (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + lines++; + } + inc_1++; + } + + if (!lines) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + getthisline = Q_irand(0, (lines+1)); + + if (getthisline < 1) + { + getthisline = 1; + } + if (getthisline > lines) + { + getthisline = lines; + } + + checkedline = 1; + + inc_1 = 0; + + while (checkedline != getthisline) + { + if (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + inc_1++; + checkedline++; + } + } + + if (checkedline == getthisline) + { + break; + } + + inc_1++; + } + + //we're at the starting position of the desired line here + inc_2 = 0; + + while (chatgroup[inc_1] != '\n') + { + chatgroup[inc_2] = chatgroup[inc_1]; + inc_2++; + inc_1++; + } + chatgroup[inc_2] = '\0'; + + //trap_EA_Say(bs->client, chatgroup); + inc_1 = 0; + inc_2 = 0; + + if (strlen(chatgroup) > MAX_CHAT_LINE_SIZE) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + while (chatgroup[inc_1]) + { + if (chatgroup[inc_1] == '%' && chatgroup[inc_1+1] != '%') + { + inc_1++; + + if (chatgroup[inc_1] == 's' && bs->chatObject) + { + cobject = bs->chatObject; + } + else if (chatgroup[inc_1] == 'a' && bs->chatAltObject) + { + cobject = bs->chatAltObject; + } + else + { + cobject = NULL; + } + + if (cobject && cobject->client) + { + inc_n = 0; + + while (cobject->client->pers.netname[inc_n]) + { + bs->currentChat[inc_2] = cobject->client->pers.netname[inc_n]; + inc_2++; + inc_n++; + } + inc_2--; //to make up for the auto-increment below + } + } + else + { + bs->currentChat[inc_2] = chatgroup[inc_1]; + } + inc_2++; + inc_1++; + } + bs->currentChat[inc_2] = '\0'; + + if (strcmp(section, "GeneralGreetings") == 0) + { + bs->doChat = 2; + } + else + { + bs->doChat = 1; + } + bs->chatTime_stored = (strlen(bs->currentChat)*45)+Q_irand(1300, 1500); + bs->chatTime = level.time + bs->chatTime_stored; + + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + + return 1; +} + +void ParseEmotionalAttachments(bot_state_t *bs, char *buf) +{ + int i = 0; + int i_c = 0; + char tbuf[16]; + + while (buf[i] && buf[i] != '}') + { + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + if (buf[i] && buf[i] != '}') + { + i_c = 0; + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + bs->loved[bs->lovednum].name[i_c] = buf[i]; + i_c++; + i++; + } + bs->loved[bs->lovednum].name[i_c] = '\0'; + + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + i_c = 0; + + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + tbuf[i_c] = buf[i]; + i_c++; + i++; + } + tbuf[i_c] = '\0'; + + bs->loved[bs->lovednum].level = atoi(tbuf); + + bs->lovednum++; + } + else + { + break; + } + + if (bs->lovednum >= MAX_LOVED_ONES) + { + return; + } + + i++; + } +} + +int ReadChatGroups(bot_state_t *bs, char *buf) +{ + char *cgroupbegin; + int cgbplace; + int i; + + cgroupbegin = strstr(buf, "BEGIN_CHAT_GROUPS"); + + if (!cgroupbegin) + { + return 0; + } + + if (strlen(cgroupbegin) >= MAX_CHAT_BUFFER_SIZE) + { + Com_Printf(S_COLOR_RED "Error: Personality chat section exceeds max size\n"); + return 0; + } + + cgbplace = cgroupbegin - buf+1; + + while (buf[cgbplace] != '\n') + { + cgbplace++; + } + + i = 0; + + while (buf[cgbplace] && buf[cgbplace] != '\0') + { + gBotChatBuffer[bs->client][i] = buf[cgbplace]; + i++; + cgbplace++; + } + + gBotChatBuffer[bs->client][i] = '\0'; + + return 1; +} + +char personalityBuffer[131072]; + +void BotUtilizePersonality(bot_state_t *bs) +{ + fileHandle_t f; + int len, rlen; + int failed; + int i; + char *readbuf, *group; + + len = trap_FS_FOpenFile(bs->settings.personalityfile, &f, FS_READ); + + failed = 0; + + if (!f) + { + Com_Printf(S_COLOR_RED "Error: Specified personality not found\n"); + return; + } + + if (len >= 131072) + { + Com_Printf(S_COLOR_RED "Personality file exceeds maximum length\n"); + return; + } + + trap_FS_Read(personalityBuffer, len, f); + + rlen = len; + + while (len < 131072) + { //kill all characters after the file length, since sometimes FS_Read doesn't do that entirely (or so it seems) + personalityBuffer[len] = '\0'; + len++; + } + + len = rlen; + + readbuf = (char *)trap_VM_LocalTempAlloc(1024); + group = (char *)trap_VM_LocalTempAlloc(65536); + + if (!GetValueGroup(personalityBuffer, "GeneralBotInfo", group)) + { + Com_Printf(S_COLOR_RED "Personality file contains no GeneralBotInfo group\n"); + failed = 1; //set failed so we know to set everything to default values + } + + if (!failed && GetPairedValue(group, "reflex", readbuf)) + { + bs->skills.reflex = atoi(readbuf); + } + else + { + bs->skills.reflex = 100; //default + } + + if (!failed && GetPairedValue(group, "accuracy", readbuf)) + { + bs->skills.accuracy = atof(readbuf); + } + else + { + bs->skills.accuracy = 10; //default + } + + if (!failed && GetPairedValue(group, "turnspeed", readbuf)) + { + bs->skills.turnspeed = atof(readbuf); + } + else + { + bs->skills.turnspeed = 0.01f; //default + } + + if (!failed && GetPairedValue(group, "turnspeed_combat", readbuf)) + { + bs->skills.turnspeed_combat = atof(readbuf); + } + else + { + bs->skills.turnspeed_combat = 0.05f; //default + } + + if (!failed && GetPairedValue(group, "maxturn", readbuf)) + { + bs->skills.maxturn = atof(readbuf); + } + else + { + bs->skills.maxturn = 360; //default + } + + if (!failed && GetPairedValue(group, "perfectaim", readbuf)) + { + bs->skills.perfectaim = atoi(readbuf); + } + else + { + bs->skills.perfectaim = 0; //default + } + + if (!failed && GetPairedValue(group, "chatability", readbuf)) + { + bs->canChat = atoi(readbuf); + } + else + { + bs->canChat = 0; //default + } + + if (!failed && GetPairedValue(group, "chatfrequency", readbuf)) + { + bs->chatFrequency = atoi(readbuf); + } + else + { + bs->chatFrequency = 5; //default + } + + if (!failed && GetPairedValue(group, "hatelevel", readbuf)) + { + bs->loved_death_thresh = atoi(readbuf); + } + else + { + bs->loved_death_thresh = 3; //default + } + + if (!failed && GetPairedValue(group, "camper", readbuf)) + { + bs->isCamper = atoi(readbuf); + } + else + { + bs->isCamper = 0; //default + } + + if (!failed && GetPairedValue(group, "meleeSpecialist", readbuf)) + { + bs->meleeSpecialist = atoi(readbuf); + } + else + { + bs->meleeSpecialist = 0; //default + } + + i = 0; + + while (i < MAX_CHAT_BUFFER_SIZE) + { //clear out the chat buffer for this bot + gBotChatBuffer[bs->client][i] = '\0'; + i++; + } + + if (bs->canChat) + { + if (!ReadChatGroups(bs, personalityBuffer)) + { + bs->canChat = 0; + } + } + + if (GetValueGroup(personalityBuffer, "BotWeaponWeights", group)) + { + for (i=0; i < WP_NUM_WEAPONS; i++) + { + if (GetPairedValue(group, bg_weaponNames[i], readbuf)) + { + bs->botWeaponWeights[i] = atoi(readbuf); + } + } + } + + bs->lovednum = 0; + + if (GetValueGroup(personalityBuffer, "EmotionalAttachments", group)) + { + ParseEmotionalAttachments(bs, group); + } + + trap_VM_LocalTempFree(65536); + trap_VM_LocalTempFree(1024); + trap_FS_FCloseFile(f); +} diff --git a/code/game/ai_wpnav.c b/code/game/ai_wpnav.c new file mode 100644 index 0000000..ba3fab2 --- /dev/null +++ b/code/game/ai_wpnav.c @@ -0,0 +1,2341 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// ai_wpnav.c + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +float gWPRenderTime = 0; +float gDeactivated = 0; +float gBotEdit = 0; +int gWPRenderedFrame = 0; +wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +int gWPNum = 0; +int gLastPrintedIndex = -1; + +nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +int nodenum; //so we can connect broken trails + +#define MAX_FLAGSTR_SIZE 128 + +char *GetFlagStr( int flags ) +{ + char *flagstr; + int i; + + flagstr = (char *)B_TempAlloc(MAX_FLAGSTR_SIZE); + i = 0; + + if (!flags) + { + strcpy(flagstr, "none\0"); + goto fend; + } + + if (flags & WPFLAG_JUMP) + { + flagstr[i] = 'j'; + i++; + } + + if (flags & WPFLAG_DUCK) + { + flagstr[i] = 'd'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMPSTAND) + { + flagstr[i] = 'c'; + i++; + } + + if (flags & WPFLAG_WAITFORFUNC) + { + flagstr[i] = 'f'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMP) + { + flagstr[i] = 's'; + i++; + } + + if (flags & WPFLAG_ONEWAY_FWD) + { + flagstr[i] = 'x'; + i++; + } + + if (flags & WPFLAG_ONEWAY_BACK) + { + flagstr[i] = 'y'; + i++; + } + + if (flags & WPFLAG_GOALPOINT) + { + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_NOVIS) + { + flagstr[i] = 'n'; + i++; + } + + if (flags & WPFLAG_NOMOVEFUNC) + { + flagstr[i] = 'm'; + i++; + } + + if (flags & WPFLAG_RED_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'r'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = 'd'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_BLUE_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'b'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'u'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + flagstr[i] = '\0'; + + if (i == 0) + { + strcpy(flagstr, "unknown\0"); + } + +fend: + return flagstr; +} + +void G_TestLine(vec3_t start, vec3_t end, int color, int time) +{ + gentity_t *te; + + te = G_TempEntity( start, EV_TESTLINE ); + VectorCopy(start, te->s.origin); + VectorCopy(end, te->s.origin2); + te->s.weapon = color; + te->r.svFlags |= SVF_BROADCAST; +} + +void BotWaypointRender(void) +{ + int i, n; + int inc_checker; + int bestindex; + int gotbestindex; + float bestdist; + float checkdist; + gentity_t *plum; + gentity_t *viewent; + char *flagstr; + vec3_t a; + + if (!gBotEdit) + { + return; + } + + bestindex = 0; + + if (gWPRenderTime > level.time) + { + goto checkprint; + } + + gWPRenderTime = level.time + 100; + + i = gWPRenderedFrame; + inc_checker = gWPRenderedFrame; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i+1] && gWPArray[i+1]->inuse) + { + plum = G_TempEntity( gWPArray[i]->origin, EV_BOTWAYPOINT ); + VectorCopy(gWPArray[i+1]->origin, plum->s.angles); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = i; + } + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num]) + { + G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000); + } + n++; + } + + gWPRenderedFrame++; + } + else + { + gWPRenderedFrame = 0; + break; + } + + if ((i - inc_checker) > 4) + { + break; //don't render too many at once + } + i++; + } + + if (i >= gWPNum) + { + gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail + gWPRenderedFrame = 0; + } + +checkprint: + + if (!bot_wp_info.value) + { + return; + } + + viewent = &g_entities[0]; //only show info to the first client + + if (!viewent || !viewent->client) + { //client isn't in the game yet? + return; + } + + bestdist = 256; //max distance for showing point info + gotbestindex = 0; + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i]) + { + VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a); + + checkdist = VectorLength(a); + + if (checkdist < bestdist) + { + bestdist = checkdist; + bestindex = i; + gotbestindex = 1; + } + } + i++; + } + + if (gotbestindex && bestindex != gLastPrintedIndex) + { + flagstr = GetFlagStr(gWPArray[bestindex]->flags); + gLastPrintedIndex = bestindex; + Com_Printf(S_COLOR_YELLOW "Waypoint %i\nFlags - %i (%s)\nOrigin - (%i %i %i)\n", (int)(gWPArray[bestindex]->index), (int)(gWPArray[bestindex]->flags), flagstr, (int)(gWPArray[bestindex]->origin[0]), (int)(gWPArray[bestindex]->origin[1]), (int)(gWPArray[bestindex]->origin[2])); + trap_VM_LocalTempFree(MAX_FLAGSTR_SIZE); + + /* + plum = G_TempEntity( gWPArray[bestindex]->origin, EV_BOTWAYPOINT ); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = bestindex; //render it once + //Don't bother at the moment. Number display is broken. + */ + } + else if (!gotbestindex) + { + gLastPrintedIndex = -1; + } +} + +void TransferWPData(int from, int to) +{ + if (!gWPArray[to]) + { + gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[to]) + { + Com_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[to]->flags = gWPArray[from]->flags; + gWPArray[to]->weight = gWPArray[from]->weight; + gWPArray[to]->associated_entity = gWPArray[from]->associated_entity; + gWPArray[to]->disttonext = gWPArray[from]->disttonext; + gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo; + gWPArray[to]->index = to; + gWPArray[to]->inuse = gWPArray[from]->inuse; + VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin); +} + +void CreateNewWP(vec3_t origin, int flags) +{ + if (gWPNum >= MAX_WPARRAY_SIZE) + { + Com_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + Com_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = flags; + gWPArray[gWPNum]->weight = 0; //calculated elsewhere + gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[gWPNum]->forceJumpTo = 0; + gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(origin, gWPArray[gWPNum]->origin); + gWPNum++; +} + +void CreateNewWP_FromObject(wpobject_t *wp) +{ + int i; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + Com_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = wp->flags; + gWPArray[gWPNum]->weight = wp->weight; + gWPArray[gWPNum]->associated_entity = wp->associated_entity; + gWPArray[gWPNum]->disttonext = wp->disttonext; + gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo; + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(wp->origin, gWPArray[gWPNum]->origin); + gWPArray[gWPNum]->neighbornum = wp->neighbornum; + + i = wp->neighbornum; + + while (i >= 0) + { + gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num; + gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo; + + i--; + } + + if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG) + { + flagRed = gWPArray[gWPNum]; + oFlagRed = flagRed; + } + else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG) + { + flagBlue = gWPArray[gWPNum]; + oFlagBlue = flagBlue; + } + + gWPNum++; +} + +void RemoveWP(void) +{ + if (gWPNum <= 0) + { + return; + } + + gWPNum--; + + if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse) + { + return; + } + + //B_Free((wpobject_t *)gWPArray[gWPNum]); + if (gWPArray[gWPNum]) + { + memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) ); + } + + //gWPArray[gWPNum] = NULL; + + if (gWPArray[gWPNum]) + { + gWPArray[gWPNum]->inuse = 0; + } +} + +void RemoveWP_InTrail(int afterindex) +{ + int foundindex; + int foundanindex; + int didchange; + int i; + + foundindex = 0; + foundanindex = 0; + didchange = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + Com_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + Com_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + i = 0; + + while (i <= gWPNum) + { + if (gWPArray[i] && gWPArray[i]->index == foundindex) + { + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + didchange = 1; + } + else if (gWPArray[i] && didchange) + { + TransferWPData(i, i-1); + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + } + + i++; + } + gWPNum--; +} + +int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + Com_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + return 0; + } + + if (afterindex < 0 || afterindex >= gWPNum) + { + Com_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return 0; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + Com_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return 0; + } + + i = gWPNum; + + while (i >= 0) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) + { + TransferWPData(i, i+1); + } + else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) + { + i++; + + if (!gWPArray[i]) + { + gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + gWPArray[i]->flags = flags; + gWPArray[i]->weight = 0; //calculated elsewhere + gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[i]->disttonext = 0; //calculated elsewhere + gWPArray[i]->forceJumpTo = 0; + gWPArray[i]->index = i; + gWPArray[i]->inuse = 1; + VectorCopy(origin, gWPArray[i]->origin); + gWPNum++; + break; + } + + i--; + } + + return 1; +} + +void TeleportToWP(gentity_t *pl, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + if (!pl || !pl->client) + { + return; + } + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + Com_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + Com_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin); + + return; +} + +void WPFlagsModify(int wpnum, int flags) +{ + if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse) + { + Com_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum); + return; + } + + gWPArray[wpnum]->flags = flags; +} + +int NotWithinRange(int base, int extent) +{ + if (extent > base && base+5 >= extent) + { + return 0; + } + + if (extent < base && base-5 <= extent) + { + return 0; + } + + return 1; +} + +int NodeHere(vec3_t spot) +{ + int i; + + i = 0; + + while (i < nodenum) + { + if ((int)nodetable[i].origin[0] == (int)spot[0] && + (int)nodetable[i].origin[1] == (int)spot[1]) + { + if ((int)nodetable[i].origin[2] == (int)spot[2] || + ((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) || + ((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2])) + { + return 1; + } + } + i++; + } + + return 0; +} + +int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, -1, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + vec3_t a, ang, fwd; + vec3_t midpos, dmid; + float startheight, midheight, fLen; + + mins[2] = -13; + maxs[2] = 13; + + trap_Trace(&tr, org1, mins, maxs, org2, -1, MASK_SOLID); + + if (tr.fraction != 1 || tr.startsolid || tr.allsolid) + { + return 0; + } + + VectorSubtract(org2, org1, a); + + vectoangles(a, ang); + + AngleVectors(ang, fwd, NULL, NULL); + + fLen = VectorLength(a)/2; + + midpos[0] = org1[0] + fwd[0]*fLen; + midpos[1] = org1[1] + fwd[1]*fLen; + midpos[2] = org1[2] + fwd[2]*fLen; + + VectorCopy(org1, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, -1, MASK_SOLID); + + startheight = org1[2] - tr.endpos[2]; + + VectorCopy(midpos, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, -1, MASK_SOLID); + + if (tr.startsolid || tr.allsolid) + { + return 1; + } + + midheight = midpos[2] - tr.endpos[2]; + + if (midheight > startheight*2) + { + return 0; //too steep of a drop.. can't go on + } + + return 1; +} + +int ConnectTrail(int startindex, int endindex) +{ + int foundit; + int cancontinue; + int i; + int failsafe; + int successnodeindex; + int insertindex; + int prenodestart; + int extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times + float fvecmeas; + float baseheight; + vec3_t a; + vec3_t startplace, starttrace; + vec3_t mins, maxs; + vec3_t testspot; + vec3_t validspotpos; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + nodenum = 0; + foundit = 0; + + i = 0; + + successnodeindex = 0; + + while (i < MAX_NODETABLE_SIZE) //clear it out before using it + { + nodetable[i].flags = 0; + nodetable[i].index = 0; + nodetable[i].inuse = 0; + nodetable[i].neighbornum = 0; + nodetable[i].origin[0] = 0; + nodetable[i].origin[1] = 0; + nodetable[i].origin[2] = 0; + nodetable[i].weight = 0; + + extendednodes[i] = 0; + + i++; + } + + i = 0; + + Com_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex); + + VectorCopy(gWPArray[startindex]->origin, startplace); + + VectorCopy(startplace, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, startplace, NULL, NULL, starttrace, -1, MASK_SOLID); + + baseheight = startplace[2] - tr.endpos[2]; + + cancontinue = 1; + + VectorCopy(startplace, nodetable[nodenum].origin); + nodetable[nodenum].weight = 1; + nodetable[nodenum].inuse = 1; + nodetable[nodenum].index = nodenum; + nodenum++; + + while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue) + { + cancontinue = 0; + i = 0; + prenodestart = nodenum; + + while (i < prenodestart) + { + if (extendednodes[i] != 1) + { + VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a); + fvecmeas = VectorLength(a); + + if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs)) + { + foundit = 1; + successnodeindex = i; + break; + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] += TABLE_BRANCH_DISTANCE; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, -1, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; + nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] -= TABLE_BRANCH_DISTANCE; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, -1, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; + nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] += TABLE_BRANCH_DISTANCE; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, -1, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; + nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] -= TABLE_BRANCH_DISTANCE; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, -1, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; + nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + extendednodes[i] = 1; + } + + i++; + } + } + + if (!foundit) + { + Com_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex); + gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; + gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; + Com_Printf(S_COLOR_YELLOW "Since points cannot be connected, point %i has been flagged as only-forward and point %i has been flagged as only-backward.\n", startindex, endindex); + + /*while (nodenum >= 0) + { + if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2]) + { + CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags); + } + + nodenum--; + }*/ + //The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging. + + return 0; + } + + i = successnodeindex; + insertindex = startindex; + failsafe = 0; + VectorCopy(gWPArray[startindex]->origin, validspotpos); + + while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0) + { + VectorSubtract(validspotpos, nodetable[i].origin, a); + if (!nodetable[nodetable[i].neighbornum].inuse || !CanGetToVectorTravel(validspotpos, /*nodetable[nodetable[i].neighbornum].origin*/nodetable[i].origin, mins, maxs) || VectorLength(a) > 256 || (!CanGetToVectorTravel(validspotpos, gWPArray[endindex]->origin, mins, maxs) && CanGetToVectorTravel(nodetable[i].origin, gWPArray[endindex]->origin, mins, maxs)) ) + { + if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex)) + { + Com_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex); + return 0; + } + + VectorCopy(nodetable[i].origin, validspotpos); + } + + if (i == 0) + { + break; + } + + i = nodetable[i].neighbornum; + + failsafe++; + } + + Com_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex); + + return 1; +} + +int OpposingEnds(int start, int end) +{ + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) && + (gWPArray[end]->flags & WPFLAG_ONEWAY_BACK)) + { + return 1; + } + + return 0; +} + +int DoorBlockingSection(int start, int end) +{ //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open + trace_t tr; + gentity_t *testdoor; + int start_trace_index; + + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, -1, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + testdoor = &g_entities[tr.entityNum]; + + if (!testdoor) + { + return 0; + } + + if (!strstr(testdoor->classname, "func_")) + { + return 0; + } + + start_trace_index = tr.entityNum; + + trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, -1, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + if (start_trace_index == tr.entityNum) + { + return 1; + } + + return 0; +} + +int RepairPaths(void) +{ + int i; + int ctRet; + vec3_t a; + + if (!gWPNum) + { + return 0; + } + + i = 0; + + trap_Cvar_Update(&bot_wp_distconnect); + trap_Cvar_Update(&bot_wp_visconnect); + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + + if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) && + !(gWPArray[i+1]->flags & WPFLAG_JUMP) && //don't calculate on jump points because they might not always want to be visible (in cases of force jumping) + !OpposingEnds(i, i+1) && + ((VectorLength(a) > 400 && bot_wp_distconnect.value) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, -1) && bot_wp_visconnect.value) ) && + !DoorBlockingSection(i, i+1)) + { + ctRet = ConnectTrail(i, i+1); + /*if (!ctRet) + { + return 0; + }*/ //we still want to write it.. + } + } + + i++; + } + + return 1; +} + +int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + vec3_t evenorg1; + + VectorCopy(org1, evenorg1); + evenorg1[2] = org2[2]; + + trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + } + + return 0; +} + +int CanForceJumpTo(int baseindex, int testingindex, float distance) +{ +#if 0 + float heightdif; + vec3_t xy_base, xy_test, v, mins, maxs; + wpobject_t *wpBase = gWPArray[baseindex]; + wpobject_t *wpTest = gWPArray[testingindex]; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse) + { + return 0; + } + + if (distance > 400) + { + return 0; + } + + VectorCopy(wpBase->origin, xy_base); + VectorCopy(wpTest->origin, xy_test); + + xy_base[2] = xy_test[2]; + + VectorSubtract(xy_base, xy_test, v); + + if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE) + { + return 0; + } + + if ((int)wpBase->origin[2] < (int)wpTest->origin[2]) + { + heightdif = wpTest->origin[2] - wpBase->origin[2]; + } + else + { + return 0; //err.. + } + + if (heightdif < 128) + { //don't bother.. + return 0; + } + + if (heightdif > 512) + { //too high + return 0; + } + + if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, -1)) + { + return 0; + } + + if (heightdif > 400) + { + return 3; + } + else if (heightdif > 256) + { + return 2; + } + else + { + return 1; + } +#else + return 0; +#endif +} + +void CalculatePaths(void) +{ + int i; + int c; + int forceJumpable; + float nLDist; + vec3_t a; + vec3_t mins, maxs; + + if (!gWPNum) + { + return; + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + //now clear out all the neighbor data before we recalculate + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum) + { + while (gWPArray[i]->neighbornum >= 0) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0; + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + gWPArray[i]->neighbornum--; + } + gWPArray[i]->neighbornum = 0; + } + + i++; + } + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + c = 0; + + while (c < gWPNum) + { + if (gWPArray[c] && gWPArray[c]->inuse && i != c && + NotWithinRange(i, c)) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a); + + nLDist = VectorLength(a); + forceJumpable = CanForceJumpTo(i, c, nLDist); + + if ((nLDist < MAX_NEIGHBOR_LINK_DISTANCE || forceJumpable) && + ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) && + (OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, -1) || forceJumpable)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c; + if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < MAX_NEIGHBOR_LINK_DISTANCE)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR + } + else + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + } + gWPArray[i]->neighbornum++; + } + + if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE) + { + break; + } + } + c++; + } + } + i++; + } +} + +gentity_t *GetObjectThatTargets(gentity_t *ent) +{ + gentity_t *next = NULL; + + if (!ent->targetname) + { + return NULL; + } + + next = G_Find( next, FOFS(target), ent->targetname ); + + if (next) + { + return next; + } + + return NULL; +} + +float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] = +{ + 0,//WP_NONE, + + 0,//WP_KNIFE, + 0,//WP_M1911A1_PISTOL, + 1,//WP_USSOCOM_PISTOL, + 6,//WP_USAS_12_SHOTGUN, + 7,//WP_M590_SHOTGUN, + + 8,//WP_MICRO_UZI_SUBMACHINEGUN, + 8,//WP_M3A1_SUBMACHINEGUN, + 9,//WP_M4_ASSAULT_RIFLE, + 9,//WP_AK74_ASSAULT_RIFLE, + + 8,//WP_MSG90A1, + 9,//WP_M60_MACHINEGUN, + 7,//WP_MM1_GRENADE_LAUNCHER, + 7,//WP_RPG7_LAUNCHER, + 7,//WP_M67_GRENADE, + + 6,//WP_M84_GRENADE, + 6,//WP_F1_GRENADE, + 6,//WP_L2A2_GRENADE, + 6,//WP_MDN11_GRENADE, + 6,//WP_SMOHG92_GRENADE, + + 6,//WP_ANM14_GRENADE, + 6//WP_M15_GRENADE, +}; + +int GetNearestVisibleWPToItem(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && + gWPArray[i]->origin[2]-15 < org[2] && + gWPArray[i]->origin[2]+15 > org[2]) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +void CalculateWeightGoals(void) +{ //set waypoint weights depending on weapon and item placement + int i = 0; + int wpindex = 0; + gentity_t *ent; + float weight; + + trap_Cvar_Update(&bot_wp_clearweight); + + if (bot_wp_clearweight.integer) + { //if set then flush out all weight/goal values before calculating them again + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + gWPArray[i]->weight = 0; + + if (gWPArray[i]->flags & WPFLAG_GOALPOINT) + { + gWPArray[i]->flags -= WPFLAG_GOALPOINT; + } + } + + i++; + } + } + + i = 0; + + while (i < MAX_GENTITIES) + { + ent = &g_entities[i]; + + weight = 0; + + if (ent && ent->classname) + { + if (strcmp(ent->classname, "pickup_armor_big") == 0) + { + weight = 3; + } + else if (strcmp(ent->classname, "pickup_armor_medium") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "pickup_armor_small") == 0) + { + weight = 1; + } + else if (strcmp(ent->classname, "pickup_health_big") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "pickup_health_small") == 0) + { + weight = 1; + } + else if (strstr(ent->classname, "pickup_weapon_") && ent->item) + { + weight = botGlobalNavWeaponWeights[ent->item->giTag]; + } + else if (ent->item && ent->item->giType == IT_AMMO) + { + weight = 3; + } + } + + if (ent && weight) + { + wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number); + + if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) + { //found the waypoint nearest the center of this object + gWPArray[wpindex]->weight = weight; + gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT; + gWPArray[wpindex]->associated_entity = ent->s.number; + } + } + + i++; + } +} + +void CalculateJumpRoutes(void) +{ + int i = 0; + float nheightdif = 0; + float pheightdif = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_JUMP) + { + nheightdif = 0; + pheightdif = 0; + + gWPArray[i]->forceJumpTo = 0; + + if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]); + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]); + } + + if (nheightdif > pheightdif) + { + pheightdif = nheightdif; + } + + if (pheightdif) + { + if (pheightdif > 500) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR + } + else if (pheightdif > 256) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR + } + else if (pheightdif > 128) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR + } + } + } + } + + i++; + } +} + +int LoadPathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *currentVar; + char *routePath; + wpobject_t thiswp; + int len; + int i, i_cv; + int nei_num; + + i = 0; + i_cv = 0; + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + len = trap_FS_FOpenFile(routePath, &f, FS_READ); + + B_TempFree(1024); //routePath + + if (!f) + { + Com_Printf(S_COLOR_YELLOW "Bot route data not found\n"); + return 2; + } + + if (len >= 524288) + { + Com_Printf(S_COLOR_RED "Route file exceeds maximum length\n"); + return 0; + } + + fileString = (char *)B_TempAlloc(524288); + currentVar = (char *)B_TempAlloc(2048); + + trap_FS_Read(fileString, len, f); + + while (i < len) + { + i_cv = 0; + + thiswp.index = 0; + thiswp.flags = 0; + thiswp.inuse = 0; + thiswp.neighbornum = 0; + thiswp.origin[0] = 0; + thiswp.origin[1] = 0; + thiswp.origin[2] = 0; + thiswp.weight = 0; + thiswp.associated_entity = ENTITYNUM_NONE; + thiswp.forceJumpTo = 0; + thiswp.disttonext = 0; + nei_num = 0; + + while (nei_num < MAX_NEIGHBOR_SIZE) + { + thiswp.neighbors[nei_num].num = 0; + thiswp.neighbors[nei_num].forceJumpTo = 0; + + nei_num++; + } + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.index = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.flags = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.weight = atof(currentVar); + + i_cv = 0; + i++; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[0] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[1] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ')') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[2] = atof(currentVar); + + i += 4; + + while (fileString[i] != '}') + { + i_cv = 0; + while (fileString[i] != ' ' && fileString[i] != '-') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar); + + if (fileString[i] == '-') + { + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR + } + else + { + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0; + } + + thiswp.neighbornum++; + + i++; + } + + i_cv = 0; + i++; + i++; + + while (fileString[i] != '\n') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.disttonext = atof(currentVar); + + CreateNewWP_FromObject(&thiswp); + i++; + } + + B_TempFree(524288); //fileString + B_TempFree(2048); //currentVar + + trap_FS_FCloseFile(f); + + CalculateWeightGoals(); + //calculate weights for idle activity goals when + //the bot has absolutely nothing else to do + + CalculateJumpRoutes(); + //Look at jump points and mark them as requiring + //force jumping as needed + + return 1; +} + +void FlagObjects(void) +{ + int i = 0, bestindex = 0, found = 0; + float bestdist = 999999, tlen = 0; + gentity_t *flag_red, *flag_blue, *ent; + vec3_t a, mins, maxs; + trace_t tr; + + flag_red = NULL; + flag_blue = NULL; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + while (i < MAX_GENTITIES) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + flag_red = ent; + } + else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + flag_blue = ent; + } + + if (flag_red && flag_blue) + { + break; + } + } + + i++; + } + + i = 0; + + if (!flag_red || !flag_blue) + { + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_red->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG; + flagRed = gWPArray[bestindex]; + oFlagRed = flagRed; + eFlagRed = flag_red; + } + + bestdist = 999999; + bestindex = 0; + found = 0; + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG; + flagBlue = gWPArray[bestindex]; + oFlagBlue = flagBlue; + eFlagBlue = flag_blue; + } +} + +int SavePathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *storeString; + char *routePath; + vec3_t a; + float flLen; + int i, s, n; + + fileString = NULL; + i = 0; + s = 0; + + if (!gWPNum) + { + return 0; + } + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + trap_FS_FOpenFile(routePath, &f, FS_WRITE); + + B_TempFree(1024); //routePath + + if (!f) + { + Com_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n"); + return 0; + } + + if (!RepairPaths()) //check if we can see all waypoints from the last. If not, try to branch over. + { + trap_FS_FCloseFile(f); + return 0; + } + + CalculatePaths(); //make everything nice and connected before saving + + FlagObjects(); //currently only used for flagging waypoints nearest CTF flags + + fileString = (char *)B_TempAlloc(524288); + storeString = (char *)B_TempAlloc(4096); + + Com_sprintf(fileString, 524288, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen); + + i++; + + while (i < gWPNum) + { + //sprintf(fileString, "%s%i %i %f (%f %f %f) { ", fileString, gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + Com_sprintf(storeString, 4096, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen); + + strcat(fileString, storeString); + + i++; + } + + trap_FS_Write(fileString, strlen(fileString), f); + + B_TempFree(524288); //fileString + B_TempFree(4096); //storeString + + trap_FS_FCloseFile(f); + + Com_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n"); + + return 1; +} + +void LoadPath_ThisLevel(void) +{ + vmCvar_t mapname; + int i = 0; + gentity_t *ent = NULL; + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0.0, 0.0 ); + + if (LoadPathData(mapname.string) == 2) + { + //enter "edit" mode if cheats enabled? + } + + trap_Cvar_Update(&bot_wp_edit); + + if (bot_wp_edit.value) + { + gBotEdit = 1; + } + else + { + gBotEdit = 0; + } + + //set the flag entities + while (i < MAX_GENTITIES) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + eFlagRed = ent; + } + else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + eFlagBlue = ent; + } + + if (eFlagRed && eFlagBlue) + { + break; + } + } + + i++; + } +} + +int AcceptBotCommand(char *cmd, gentity_t *pl) +{ + int OptionalArgument, i; + int FlagsFromArgument; + char *OptionalSArgument, *RequiredSArgument; + vmCvar_t mapname; + + if (!gBotEdit) + { + return 0; + } + + OptionalArgument = 0; + i = 0; + FlagsFromArgument = 0; + OptionalSArgument = NULL; + RequiredSArgument = NULL; + + //if a waypoint editing related command is issued, bots will deactivate. + //once bot_wp_save is issued and the trail is recalculated, bots will activate again. + + if (!pl || !pl->client) + { + return 0; + } + + if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands. + { + Com_Printf(S_COLOR_YELLOW "bot_wp_add" S_COLOR_WHITE " - Add a waypoint (optional int parameter will insert the point after the specified waypoint index in a trail)\n\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_rem" S_COLOR_WHITE " - Remove a waypoint (removes last unless waypoint index is specified as a parameter)\n\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_addflagged" S_COLOR_WHITE " - Same as wp_add, but adds a flagged point (type bot_wp_addflagged for help)\n\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_switchflags" S_COLOR_WHITE " - Switches flags on an existing waypoint (type bot_wp_switchflags for help)\n\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_killoneways" S_COLOR_WHITE " - Removes oneway (backward and forward) flags on all waypoints in the level\n\n"); + Com_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n"); + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_add") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, 0); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_rem") == 0) + { + gDeactivated = 1; + + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + RemoveWP_InTrail(OptionalArgument); + } + else + { + RemoveWP(); + } + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_tele") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + TeleportToWP(pl, OptionalArgument); + } + else + { + Com_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n"); + TeleportToWP(pl, gWPNum-1); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_addflagged") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + Com_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_addflagged\nj - Jump point\nd - Duck point\nc - Snipe or camp standing\nf - Wait for func\nm - Do not move to when func is under\ns - Snipe or camp\nx - Oneway, forward\ny - Oneway, back\ng - Mission goal\nn - No visibility\nExample (for a point the bot would jump at, and reverse on when traveling a trail backwards):\nbot_wp_addflagged jx\n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, FlagsFromArgument); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_switchflags") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + Com_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_switchflags\nType bot_wp_addflagged for a list of flags and their corresponding characters, or use 0 for no flags.\nSyntax: bot_wp_switchflags \n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + WPFlagsModify(OptionalArgument, FlagsFromArgument); + } + else + { + Com_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags \n"); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_killoneways") == 0) + { + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD; + } + if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK; + } + } + + i++; + } + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_save") == 0) + { + gDeactivated = 0; + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0.0, 0.0 ); + SavePathData(mapname.string); + return 1; + } + + return 0; +} diff --git a/code/game/anims.h b/code/game/anims.h new file mode 100644 index 0000000..e69de29 diff --git a/code/game/be_aas.h b/code/game/be_aas.h new file mode 100644 index 0000000..55bc42a --- /dev/null +++ b/code/game/be_aas.h @@ -0,0 +1,205 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /source/code/botlib/be_aas.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +typedef enum +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int gametypeitems; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/code/game/be_ai_char.h b/code/game/be_ai_char.h new file mode 100644 index 0000000..75d6f9e --- /dev/null +++ b/code/game/be_ai_char.h @@ -0,0 +1,32 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/code/game/be_ai_chat.h b/code/game/be_ai_chat.h new file mode 100644 index 0000000..f393553 --- /dev/null +++ b/code/game/be_ai_chat.h @@ -0,0 +1,97 @@ +// Copyright (C) 2001-2002 Raven Software +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/code/game/be_ai_gen.h b/code/game/be_ai_gen.h new file mode 100644 index 0000000..f0a7fb8 --- /dev/null +++ b/code/game/be_ai_gen.h @@ -0,0 +1,17 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/code/game/be_ai_goal.h b/code/game/be_ai_goal.h new file mode 100644 index 0000000..cf3d20a --- /dev/null +++ b/code/game/be_ai_goal.h @@ -0,0 +1,102 @@ +// Copyright (C) 2001-2002 Raven Software +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/code/game/be_ai_move.h b/code/game/be_ai_move.h new file mode 100644 index 0000000..9dd256d --- /dev/null +++ b/code/game/be_ai_move.h @@ -0,0 +1,126 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/code/game/be_ai_weap.h b/code/game/be_ai_weap.h new file mode 100644 index 0000000..76a75eb --- /dev/null +++ b/code/game/be_ai_weap.h @@ -0,0 +1,88 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/code/game/be_ea.h b/code/game/be_ea.h new file mode 100644 index 0000000..0f75fc5 --- /dev/null +++ b/code/game/be_ea.h @@ -0,0 +1,52 @@ +// Copyright (C) 2001-2002 Raven Software +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 10/05/99 3:42p $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_Attack(int client); +void EA_Alt_Attack(int client); +void EA_ForcePower(int client); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/code/game/bg_gametype.c b/code/game/bg_gametype.c new file mode 100644 index 0000000..da7c7f3 --- /dev/null +++ b/code/game/bg_gametype.c @@ -0,0 +1,265 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_gametype.c -- dynamic gametype handling + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +gametypeData_t bg_gametypeData[MAX_GAMETYPES]; +int bg_gametypeCount; + +/* +=============== +BG_ParseGametypePhotos + +Parses the photo information for objectives dialog +=============== +*/ +static qboolean BG_ParseGametypePhotos ( int gametypeIndex, TGPGroup group ) +{ + TGPGroup photo; + int index; + char temp[MAX_TOKENLENGTH]; + + // Convienience check + if ( !group ) + { + return qtrue; + } + + index = 0; + photo = trap_GPG_GetSubGroups ( group ); + + while ( photo ) + { + trap_GPG_GetName ( photo, temp ); + bg_gametypeData[gametypeIndex].photos[index].name = trap_VM_LocalStringAlloc ( temp ); + + trap_GPG_FindPairValue ( photo, "displayname", "unknown", temp ); + bg_gametypeData[gametypeIndex].photos[index].displayName = trap_VM_LocalStringAlloc ( temp ); + + index++; + + photo = trap_GPG_GetNext ( photo ); + } + + return qtrue; +} + +/* +=============== +BG_ParseGametypeInfo + +Parses minimal information about the given gametype. For the most part +this information is the name of the gametype and some of its parameters. +=============== +*/ +static qboolean BG_ParseGametypeInfo ( int gametypeIndex ) +{ + gametypeData_t* gametype; + TGenericParser2 GP2; + TGPGroup topGroup; + TGPGroup gtGroup; + char temp[1024]; + + // Get the pointer for the gametype data + gametype = &bg_gametypeData[gametypeIndex]; + + // Open the gametype's script file + GP2 = trap_GP_ParseFile( (char*)gametype->script, qtrue, qfalse); + if (!GP2) + { + return qfalse; + } + + // Top group should only contain the "gametype" sub group + topGroup = trap_GP_GetBaseParseGroup(GP2); + if ( !topGroup ) + { + return qfalse; + } + + // Grab the gametype sub group + gtGroup = trap_GPG_FindSubGroup ( topGroup, "gametype" ); + if ( !gtGroup ) + { + return qfalse; + } + + // Parse out the name of the gametype + trap_GPG_FindPairValue ( gtGroup, "displayname", "", temp ); + if ( !temp[0] ) + { + return qfalse; + } + gametype->displayName = trap_VM_LocalStringAlloc ( temp ); + + // Description + trap_GPG_FindPairValue ( gtGroup, "description", "", temp ); + if ( temp[0] ) + { + gametype->description = trap_VM_LocalStringAlloc ( temp ); + } + + // Are pickups enabled? + trap_GPG_FindPairValue ( gtGroup, "pickups", "yes", temp ); + if ( !Q_stricmp ( temp, "no" ) ) + { + gametype->pickupsDisabled = qtrue; + } + + // Are teams enabled? + trap_GPG_FindPairValue ( gtGroup, "teams", "yes", temp ); + if ( !Q_stricmp ( temp, "yes" ) ) + { + gametype->teams = qtrue; + } + + // Display kills + trap_GPG_FindPairValue ( gtGroup, "showkills", "no", temp ); + if ( !Q_stricmp ( temp, "yes" ) ) + { + gametype->showKills = qtrue; + } + + // Look for the respawn type + trap_GPG_FindPairValue ( gtGroup, "respawn", "normal", temp ); + if ( !Q_stricmp ( temp, "none" ) ) + { + gametype->respawnType = RT_NONE; + } + else if ( !Q_stricmp ( temp, "interval" ) ) + { + gametype->respawnType = RT_INTERVAL; + } + else + { + gametype->respawnType = RT_NORMAL; + } + + // What percentage doest he backpack replenish? + trap_GPG_FindPairValue ( gtGroup, "backpack", "0", temp ); + gametype->backpack = atoi(temp); + + // Get the photo information for objectives dialog + BG_ParseGametypePhotos ( gametypeIndex, trap_GPG_FindSubGroup ( gtGroup, "photos" ) ); + + // Cleanup the generic parser + trap_GP_Delete ( &GP2 ); + + return qtrue; + +} + +/* +=============== +BG_BuildGametypeList + +Builds a list of the gametypes that are available and parses minimal +information about those gametypes. +=============== +*/ +qboolean BG_BuildGametypeList ( void ) +{ + char filename[MAX_QPATH]; + char filelist[4096]; + char* fileptr; + char* s; + int filelen; + int filecount; + int i; + + bg_gametypeCount = 0; + + // Retrieve the list of gametype files. The returned list is a + // null separated list with the number of entries returned by the call + filecount = trap_FS_GetFileList("scripts", ".gametype", filelist, 4096 ); + fileptr = filelist; + + for ( i = 0; i < filecount; i++, fileptr += filelen+1) + { + // Grab the length so we can skip this file later + filelen = strlen(fileptr); + + // Build the full filename + strcpy(filename, "scripts/"); + strcat(filename, fileptr ); + + // Fill in what we know so far + bg_gametypeData[bg_gametypeCount].script = trap_VM_LocalStringAlloc ( filename ); + + // Kill the dot so we can use the filename as the short name + s = strchr ( fileptr, '.' ); + *s = '\0'; + bg_gametypeData[bg_gametypeCount].name = trap_VM_LocalStringAlloc ( fileptr ); + + // TODO: Parse the gametype file + BG_ParseGametypeInfo ( bg_gametypeCount++ ); + } + + return qtrue; +} + +/* +=============== +BG_FindGametype + +Returns the gametype index using the given name. If the gametype isnt found +then -1 will be returned (and this is bad) +=============== +*/ +int BG_FindGametype ( const char* name ) +{ + int i; + + // Loop through the known gametypes and compare their names to + // the name given + for ( i = 0; i < bg_gametypeCount; i ++ ) + { + // Do the names match? + if ( !Q_stricmp ( bg_gametypeData[i].name, name ) ) + { + return i; + } + } + + return -1; +} + +/* +============== +BG_FindGametypeItem + +Search through the item list for the gametype item with +the given index. +============== +*/ +gitem_t *BG_FindGametypeItem ( int index ) +{ + return &bg_itemlist[index + MODELINDEX_GAMETYPE_ITEM]; +} + +/* +============== +BG_FindGametypeItemByID + +Gametype will assign ids to the gametype items for them for future reference, the +id is crammed into the quantity field of the gametype item. This function will +find the gametype item with the given item id. +============== +*/ +gitem_t *BG_FindGametypeItemByID ( int itemid ) +{ + int i; + + for ( i = 0; i < MAX_GAMETYPE_ITEMS; i ++ ) + { + if ( bg_itemlist[i + MODELINDEX_GAMETYPE_ITEM].quantity == itemid ) + { + return &bg_itemlist[i + MODELINDEX_GAMETYPE_ITEM]; + } + } + + return NULL; +} diff --git a/code/game/bg_lib.c b/code/game/bg_lib.c new file mode 100644 index 0000000..d77be3b --- /dev/null +++ b/code/game/bg_lib.c @@ -0,0 +1,1316 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$Id: bg_lib.c,v 1.23 2000/02/04 06:46:50 zoid Exp $"; +#endif /* LIBC_SCCS and not lint */ + +// bk001127 - needed for DLL's +#if !defined( Q3_VM ) +typedef int cmp_t(const void *, const void *); +#endif + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) (a) < (b) ? a : b +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void swapfunc( char* a, char* b, int n, int swaptype) +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char *med3(char* a, char* b, char* c, cmp_t* cmp) +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void qsort( void* a, size_t n, size_t es, cmp_t* cmp) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' +#if defined ( Q3_VM ) + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + while ( *string ) { + if ( *string == c ) { + return ( char * )string; + } + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} +#endif // bk001211 + +// bk001120 - presumably needed for Mac +//#if !defined(_MSC_VER) && !defined(__linux__) +// bk001127 - undid undo +#if defined ( Q3_VM ) +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +#endif +//#ifndef _MSC_VER + +void *memmove( void *dest, const void *src, size_t count ) { + int i; + + if ( dest > src ) { + for ( i = count-1 ; i >= 0 ; i-- ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } else { + for ( i = 0 ; i < count ; i++ ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +} + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) { + return sin(x) / cos(x); +} +#endif + + +static int randSeed = 0; + +void srand( unsigned seed ) { + randSeed = seed; +} + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + float sign; + float value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[0]; + if ( c != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } else { + string++; + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) { + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +// bk001120 - presumably needed for Mac +//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +// bk001127 - undid undo +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) { + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#define LADJUST 0x00000004 /* left adjustment */ +#define LONGDBL 0x00000008 /* long double */ +#define LONGINT 0x00000010 /* long integer */ +#define QUADINT 0x00000020 /* quad integer */ +#define SHORTINT 0x00000040 /* short integer */ +#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ +#define FPT 0x00000100 /* floating point number */ + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + if( !( flags & LADJUST ) ) { + while ( digits < width ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while ( digits-- ) { + *buf++ = text[digits]; + width--; + } + + if( flags & LADJUST ) { + while ( width-- ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + // write the float number + digits = 0; + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; + + if (prec < 0) + prec = 6; + // write the fraction + digits = 0; + while (digits < prec) { + fval -= (int) fval; + fval *= 10.0; + val = (int) fval; + text[digits++] = '0' + val % 10; + } + + if (digits > 0) { + buf = *buf_p; + *buf++ = '.'; + for (prec = 0; prec < digits; prec++) { + *buf++ = text[prec]; + } + *buf_p = buf; + } +} + + +void AddString( char **buf_p, char *string, int width, int prec ) { + int size; + char *buf; + + buf = *buf_p; + + if ( string == NULL ) { + string = "(null)"; + prec = -1; + } + + if ( prec >= 0 ) { + for( size = 0; size < prec; size++ ) { + if( string[size] == '\0' ) { + break; + } + } + } + else { + size = strlen( string ); + } + + width -= size; + + while( size-- ) { + *buf++ = *string++; + } + + while( width-- > 0 ) { + *buf++ = ' '; + } + + *buf_p = buf; +} + +/* +vsprintf + +I'm not going to support a bunch of the more arcane stuff in here +just to keep it simpler. For example, the '*' and '$' are not +currently supported. I've tried to make it so that it will just +parse and ignore formats we don't support. +*/ +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + int *arg; + char *buf_p; + char ch; + int flags; + int width; + int prec; + int n; + char sign; + + buf_p = buffer; + arg = (int *)argptr; + + while( qtrue ) { + // run through the format string until we hit a '%' or '\0' + for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p++ = ch; + } + if ( ch == '\0' ) { + goto done; + } + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +rflag: + ch = *fmt++; +reswitch: + switch( ch ) { + case '-': + flags |= LADJUST; + goto rflag; + case '.': + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; + case '0': + flags |= ZEROPAD; + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + n = 10 * n + ( ch - '0' ); + ch = *fmt++; + } while( is_digit( ch ) ); + width = n; + goto reswitch; + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + case 'f': + AddFloat( &buf_p, *(double *)arg, width, prec ); +#ifdef __LCC__ + arg += 1; // everything is 32 bit in my compiler +#else + arg += 2; +#endif + break; + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + case '%': + *buf_p++ = ch; + break; + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) { + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + fmt++; + continue; + } + + cmd = fmt[1]; + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + arg++; + } + + return count; +} + +#endif diff --git a/code/game/bg_lib.h b/code/game/bg_lib.h new file mode 100644 index 0000000..ee4815b --- /dev/null +++ b/code/game/bg_lib.h @@ -0,0 +1,71 @@ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MIN (-128) /* minimum signed char value */ +#define SCHAR_MAX 127 /* maximum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ +#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t(const void *, const void *); +void qsort(void *a, size_t n, size_t es, cmp_t *cmp); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); +double asin( double x ); + diff --git a/code/game/bg_local.h b/code/game/bg_local.h new file mode 100644 index 0000000..b073e04 --- /dev/null +++ b/code/game/bg_local.h @@ -0,0 +1,70 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes +#define MIN_WALK_NORMAL_TERRAIN 0.625f // bit steeper for terrain + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001f + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct +{ + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); +void PM_AddEventWithParm( int newEvent, int parm ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + +void PM_StartTorsoAnim ( playerState_t* ps, int anim, int time ); +void PM_ContinueLegsAnim ( playerState_t* ps, int anim ); +void PM_ForceLegsAnim ( playerState_t* ps, int anim ); +void PM_TorsoAnimation ( playerState_t* ps ); +void PM_SetAnim ( playerState_t* ps, int setAnimParts,int anim,int setAnimFlags, int blendTime); + diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c new file mode 100644 index 0000000..d1fb7a3 --- /dev/null +++ b/code/game/bg_misc.c @@ -0,0 +1,1622 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + +gitem_t bg_itemlist[] = +{ + { + NULL, // classname + NULL, // pickup_sound + { NULL, // world_model[0] + NULL, // world_model[1] + 0, 0} , // world_model[2],[3] +/* icon */ NULL, // icon + NULL, + NULL, +/* pickup */ NULL, // pickup_name + 0, // quantity + 0, // giType (IT_*) + 0, // giTag +/* precache */ "", // precaches +/* sounds */ "" // sounds + }, // leave index 0 alone + + // + // Pickups + // + +/*QUAKED pickup_armor_big (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_armor_big", + "sound/player/pickup/armour.wav", + { "models/pick_ups/armor_large.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/armor_large_icon", + "", + "some", +/* pickup */ "Heavy Armor", + 75, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "", + + }, + +/*QUAKED pickup_armor_medium (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_armor_medium", + "sound/player/pickup/armour.wav", + { "models/pick_ups/armor_medium.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/armor_medium_icon", + "", + "some", +/* pickup */ "Medium Armor", + 50, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "", + + }, + +/*QUAKED pickup_armor_small (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_armor_small", + "sound/player/pickup/armour.wav", + { "models/pick_ups/armor_small.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/armor_small_icon", + "", + "some", +/* pickup */ "Small Armor", + 25, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "", + + }, + +/*QUAKED pickup_health_big (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_health_big", + "sound/player/pickup/health.wav", + { "models/pick_ups/health_lrg.md3", + 0, 0, 0 }, +/* icon */ "gfx/menus/hud/weapon_icons/health_large_icon", + "", + "a", +/* pickup */ "Large Health", + 100, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED pickup_health_small (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_health_small", + "sound/player/pickup/health.wav", + { "models/pick_ups/health_smll.md3", + 0, 0, 0 }, +/* icon */ "gfx/menus/hud/weapon_icons/health_small_icon", + "", + "a", +/* pickup */ "Small Health", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // ITEMS + // + + + // + // WEAPONS + // + +/*Q U A K E D weapon_knife (.3 .3 1) (-15 -15 -15) (15 15 15) suspended +Don't place this +*/ + { + "weapon_knife", + "sound/weapons/knife/ready.wav", + { "models/weapons/knife/world/knifeworld.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/knife_icon", + "*gfx/menus/weapon_renders/knife", + "a", +/* pickup */ "Knife", + -1, + IT_WEAPON, + WP_KNIFE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_KNIFE + }, + +/*QUAKED pickup_weapon_US_SOCOM (0 .6 .6) (-15 -15 -15) (15 15 15) +Pistol, uses 45 rounds +*/ + { + "pickup_weapon_US_SOCOM", + "sound/player/pickup/weapon.wav", + { "models/weapons/ussocom/world/ussocomworld.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ussocom_icon", + "*gfx/menus/weapon_renders/ussocom_mp", + "a", +/* pickup */ "US SOCOM", + 10, + IT_WEAPON, + WP_USSOCOM_PISTOL, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PISTOL, + }, + +/*QUAKED pickup_weapon_M19 (0 .6 .6) (-15 -15 -15) (15 15 15) +Pistol, uses 45 rounds +*/ + { + "pickup_weapon_M19", + "sound/player/pickup/weapon.wav", + { "models/weapons/m1911a1/world/m1911a1world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m1911a1_icon", + "*gfx/menus/weapon_renders/m1911a1", + "a", +/* pickup */ "M1911A1", + 12, + IT_WEAPON, + WP_M1911A1_PISTOL, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PISTOL, + }, + +/*QUAKED pickup_weapon_microuzi (0 .6 .6) (-15 -15 -15) (15 15 15) +Sub-Machinegun, uses 9mm rounds +*/ + { + "pickup_weapon_microuzi", + "sound/player/pickup/weapon.wav", + { "models/weapons/microuzi/world/microuziworld.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/microuzi_icon", + "*gfx/menus/weapon_renders/microuzi", + "a", +/* pickup */ "MicroUzi", + 30, + IT_WEAPON, + WP_MICRO_UZI_SUBMACHINEGUN, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_SECONDARY, + }, + +/*QUAKED pickup_weapon_M3A1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Sub-Machinegun, uses 45 rounds +*/ + { + "pickup_weapon_M3A1", + "sound/player/pickup/weapon.wav", + { "models/weapons/m3a1/world/m3a1world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m3a1_icon", + "gfx/menus/weapon_renders/m3a1", + "a", +/* pickup */ "M3A1 Sub-machinegun", + 30, + IT_WEAPON, + WP_M3A1_SUBMACHINEGUN, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_SECONDARY, + }, + +/*QUAKED pickup_weapon_USAS_12 (0 .6 .6) (-15 -15 -15) (15 15 15) +Shotgun, uses 12-gauge rounds +ammo ---------- amount of ammo (defaults to 10) +*/ + { + "pickup_weapon_USAS_12", + "sound/player/pickup/weapon.wav", + { "models/weapons/usas12/world/usas12world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/usas12_icon", + "gfx/menus/weapon_renders/usas12", + "a", +/* pickup */ "USAS12 SHOTGUN", + 30, + IT_WEAPON, + WP_USAS_12_SHOTGUN, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_M590 (0 .6 .6) (-15 -15 -15) (15 15 15) +Shotgun, uses 12-gauge rounds +*/ + { + "pickup_weapon_M590", + "sound/player/pickup/weapon.wav", + { "models/weapons/m590/world/m590world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m590_icon", + "gfx/menus/weapon_renders/m590", + "a", +/* pickup */ "M590", + 30, + IT_WEAPON, + WP_M590_SHOTGUN, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_SECONDARY, + }, + +/*QUAKED pickup_weapon_MSG90A1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Sniper Rifle, uses 7.62 rounds +*/ + { + "pickup_weapon_MSG90A1", + "sound/player/pickup/weapon.wav", + { "models/weapons/msg90a1/world/msg90a1world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/msg90a1_icon", + "gfx/menus/weapon_renders/msg90a1", + "a", +/* pickup */ "MSG90A1 Sniper", + 30, + IT_WEAPON, + WP_MSG90A1, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_M4 (0 .6 .6) (-15 -15 -15) (15 15 15) +Assault Rifle, uses 5.56 rounds and 40mm grenades +*/ + { + "pickup_weapon_M4", + "sound/player/pickup/weapon.wav", + { "models/weapons/m4/world/m4world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m4_icon", + "gfx/menus/weapon_renders/m4_m203", + "a", +/* pickup */ "M4 Assault", + 30, + IT_WEAPON, + WP_M4_ASSAULT_RIFLE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_AK_74 (0 .6 .6) (-15 -15 -15) (15 15 15) +Assault Rifle, uses 5.56 rounds +*/ + { + "pickup_weapon_AK_74", + "sound/player/pickup/weapon.wav", + { "models/weapons/ak74/world/ak74world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ak74_icon", + "gfx/menus/weapon_renders/ak74", + "an", +/* pickup */ "AK74 Assault", + 30, + IT_WEAPON, + WP_AK74_ASSAULT_RIFLE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_M60 (0 .6 .6) (-15 -15 -15) (15 15 15) +Machinegun, uses 7.62 rounds +*/ + { + "pickup_weapon_M60", + "sound/player/pickup/weapon.wav", + { "models/weapons/m60/world/m60world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m60_icon", + "gfx/menus/weapon_renders/m60", + "a", +/* pickup */ "M60 Machinegun", + 30, + IT_WEAPON, + WP_M60_MACHINEGUN, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_RPG_7 (0 .6 .6) (-15 -15 -15) (15 15 15) +RPG, uses 40mm rounds +*/ + { + "pickup_weapon_RPG_7", + "sound/player/pickup/weapon.wav", + { "models/weapons/rpg7/world/rpg7world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/rpg7_icon", + "gfx/menus/weapon_renders/rpg7", + "a", +/* pickup */ "RPG-7", + 10, + IT_WEAPON, + WP_RPG7_LAUNCHER, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_MM_1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade Launcher, uses 40mm rounds +*/ + { + "pickup_weapon_MM_1", + "sound/player/pickup/weapon.wav", + { "models/weapons/mm1/world/mm1world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/mm1_icon", + "gfx/menus/weapon_renders/mm1", + "a", +/* pickup */ "MM1 Grenade Launcher", + 10, + IT_WEAPON, + WP_MM1_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_PRIMARY, + }, + +/*QUAKED pickup_weapon_M67 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_M67", + "sound/player/pickup/weapon.wav", + { "models/weapons/m67/world/m67world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m67_icon", + "*gfx/menus/weapon_renders/m67", + "a", +/* pickup */ "M67 grenade", + 5, + IT_WEAPON, + WP_M67_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_M84 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_M84", + "sound/player/pickup/weapon.wav", + { "models/weapons/m84/world/m84world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m84_icon", + "*gfx/menus/weapon_renders/m84", + "a", +/* pickup */ "M84 Flash", + 5, + IT_WEAPON, + WP_M84_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_F1 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_F1", + "sound/player/pickup/weapon.wav", + { "models/weapons/f1/world/f1world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/f1_icon", + "*gfx/menus/weapon_renders/m84", + "a", +/* pickup */ "F1 Frag", + 5, + IT_WEAPON, + WP_F1_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_L2A2 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_L2A2", + "sound/player/pickup/weapon.wav", + { "models/weapons/l2a2/world/l2a2world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/l2a2_icon", + "*gfx/menus/weapon_renders/m84", + "a", +/* pickup */ "L2A2 grenade", + 5, + IT_WEAPON, + WP_L2A2_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_MDN11 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_MDN11", + "sound/player/pickup/weapon.wav", + { "models/weapons/mdn11/world/mdn11world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/mdn11_icon", + "*gfx/menus/weapon_renders/mdn11", + "a", +/* pickup */ "MDN11 grenade", + 5, + IT_WEAPON, + WP_MDN11_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_SMOHG92 (0 .6 .6) (-15 -15 -15) (15 15 15) +Grenade +*/ + { + "pickup_weapon_SMOHG92", + "sound/player/pickup/weapon.wav", + { "models/weapons/smohg92/world/smohg92world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/smohg92_icon", + "*gfx/menus/weapon_renders/smohg92", + "a", +/* pickup */ "SMOHG92 Frag", + 5, + IT_WEAPON, + WP_SMOHG92_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_AN_M14 (0 .6 .6) (-15 -15 -15) (15 15 15) +Incendiary Grenade +*/ + { + "pickup_weapon_AN_M14", + "sound/player/pickup/weapon.wav", + { "models/weapons/anm14/world/anm14world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/anm14_icon", + "*gfx/menus/weapon_renders/anm14", + "an", +/* pickup */ "ANM14 Incendiary", + 5, + IT_WEAPON, + WP_ANM14_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + +/*QUAKED pickup_weapon_M15 (0 .6 .6) (-15 -15 -15) (15 15 15) +White Phosphorus Grenade +*/ + { + "pickup_weapon_M15", + "sound/player/pickup/weapon.wav", + { "models/weapons/m15/world/m15world.glm", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/m15_icon", + "*gfx/menus/weapon_renders/m15", + "a", +/* pickup */ "M15 Smoke", + 5, + IT_WEAPON, + WP_M15_GRENADE, +/* precache */ "", +/* sounds */ "", + + OUTFITTING_GROUP_GRENADE, + }, + + // + // AMMO ITEMS + // + +/*QUAKED pickup_ammo_45 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_45", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_45_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_45_icon", + "", + "some", +/* pickup */ "0.45 ACP Ammo", + 30, + IT_AMMO, + AMMO_045, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED pickup_ammo_9mm (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_9mm", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_9mm_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_9mm_icon", + "", + "some", +/* pickup */ "9mm Ammo", + 30, + IT_AMMO, + AMMO_9, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED pickup_ammo_12gauge (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_12gauge", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_shotgun_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_shotgun_icon", + "", + "some", +/* pickup */ "Shotgun Ammo", + 10, + IT_AMMO, + AMMO_12, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED pickup_ammo_762 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_762", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_762_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_762_icon", + "", + "some", +/* pickup */ "7.62mm Ammo", + 30, + IT_AMMO, + AMMO_762, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED pickup_ammo_556 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_556", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_556_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_556_icon", + "", + "some", +/* pickup */ "5.56mm Ammo", + 30, + IT_AMMO, + AMMO_556, +/* precache */ "", +/* sounds */ "", + }, + +/*QUAKED pickup_ammo_40mm (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_40mm", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_40_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_40mm_icon", + "", + "some", +/* pickup */ "40mm Grenade Ammo", + 5, + IT_AMMO, + AMMO_40, +/* precache */ "", +/* sounds */ "", + }, + + +/*QUAKED pickup_ammo_rpg7 (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { + "pickup_ammo_rpg7", + "sound/player/pickup/ammo.wav", + { "models/pick_ups/ammo_rpg7_smll.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/ammo_rpg_icon", + "", + "some", +/* pickup */ "RPG7 Ammo", + 5, + IT_AMMO, + AMMO_RPG7, +/* precache */ "", +/* sounds */ "", + }, + + +/*QUAKED pickup_backpack (0 .6 .6) (-15 -15 -15) (15 15 15) +*/ + { // just a temp place holder + "pickup_backpack", + "sound/player/pickup/health.wav", + { "models/pick_ups/mp_universal_pickup.md3", + 0, 0, 0}, +/* icon */ "gfx/menus/hud/weapon_icons/mp_universal_pickup", + "", + "a ", +/* pickup */ "Backpack", + 0, + IT_BACKPACK, + 0, +/* precache */ "", +/* sounds */ "", + }, + + { + "gametype_item_1", + NULL, + { 0 }, + "", + "", + "the", + "", + 0, + IT_GAMETYPE, + 0, + "", + "" + }, + + { + "gametype_item_2", + NULL, + { 0 }, + "", + "", + "the", + "", + 0, + IT_GAMETYPE, + 1, + "", + "" + }, + + { + "gametype_item_3", + NULL, + { 0 }, + "", + "", + "the", + "", + 0, + IT_GAMETYPE, + 2, + "", + "" + }, + + { + "gametype_item_4", + NULL, + { 0 }, + "", + "", + "the", + "", + 0, + IT_GAMETYPE, + 3, + "", + "" + }, + + { + "gametype_item_5", + NULL, + { 0 }, + "", + "", + "the", + "", + 0, + IT_GAMETYPE, + 4, + "", + "" + }, + + { + "armor", + "sound/player/pickup/ammo.wav", + { 0 }, + "gfx/menus/hud/weapon_icons/armor_icon", + "*gfx/menus/weapon_renders/armor", + "some", + "Armor", + 5, + IT_PASSIVE, + 0, + "", + "", + + OUTFITTING_GROUP_ACCESSORY + }, + + { + "venhance_night_vision", + "sound/player/pickup/ammo.wav", + { 0 }, + "gfx/menus/hud/weapon_icons/nightvision_icon", + "*gfx/menus/weapon_renders/nightvision", + "some", + "NV. Goggles", + 5, + IT_PASSIVE, + 0, + "", + "", + + OUTFITTING_GROUP_ACCESSORY + }, + + { + "venhance_thermal", + "sound/player/pickup/ammo.wav", + { 0 }, + "gfx/menus/hud/weapon_icons/thermal_icon", + "*gfx/menus/weapon_renders/thermal", + "some", + "Thermal Goggles", + 5, + IT_PASSIVE, + 0, + "", + "", + + OUTFITTING_GROUP_ACCESSORY + }, + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + + +/* +=============== +BG_FindWeaponItem +=============== +*/ +gitem_t *BG_FindWeaponItem ( weapon_t weapon ) +{ + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) + { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) + { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +/* +=============== +BG_FindItem +=============== +*/ +gitem_t *BG_FindItem( const char *pickupName ) +{ + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) + { + if ( !Q_stricmp( it->pickup_name, pickupName ) ) + { + return it; + } + } + + return NULL; +} +/* +=============== +BG_FindClassnameItem +=============== +*/ +gitem_t *BG_FindClassnameItem ( const char *classname ) +{ + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) + { + if ( !Q_stricmp( it->classname, classname ) ) + { + return it; + } + } + + return NULL; +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) +{ + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 36 || + ps->origin[0] - origin[0] < -36 || + ps->origin[1] - origin[1] > 36 || + ps->origin[1] - origin[1] < -36 || + ps->origin[2] - origin[2] > 55 || + ps->origin[2] - origin[2] < -50 ) + { + return qfalse; + } + + return qtrue; +} + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) +{ + gitem_t *item; + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) + { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + // Can the item be picked up yet? + if ( ent->eFlags & EF_NOPICKUP ) + { + return qfalse; + } + + switch( item->giType ) + { + case IT_WEAPON: + + // See if this player is under limited inventory restrictions. The truth is that + // all players on the server will be under the same restrictions, but by doing it this + // way its easy to get info to the bg-code. + if ( ps->pm_flags & PMF_LIMITED_INVENTORY ) + { + int primary = 0; + int secondary = 0; + weapon_t weapon; + + switch ( item->outfittingGroup ) + { + case OUTFITTING_GROUP_PRIMARY: + primary++; + break; + + case OUTFITTING_GROUP_SECONDARY: + secondary++; + break; + + // We only have restrictions on primary and secondary items + default: + return qtrue; + } + + for ( weapon = WP_KNIFE + 1; weapon < WP_NUM_WEAPONS; weapon ++ ) + { + gitem_t* witem; + + if ( !( ps->stats[STAT_WEAPONS] & (1<outfittingGroup ) + { + case OUTFITTING_GROUP_PRIMARY: + primary++; + break; + + case OUTFITTING_GROUP_SECONDARY: + secondary++; + break; + } + } + + // Cant hold 2 of either primary or secondary + if ( primary > 1 || secondary > 1 ) + { + return qfalse; + } + + return qtrue; + } + + // If you dont have it then pick it up + if ( !(ps->stats[STAT_WEAPONS] & (1<giTag) ) ) + { + return qtrue; + } + + // If you already have the weapon and have full ammo, then dont pick it up + if ( ps->ammo[weaponData[item->giTag].attack[ATTACK_NORMAL].ammoIndex] >= ammoData[weaponData[item->giTag].attack[ATTACK_NORMAL].ammoIndex].max) + { + if ( BG_WeaponHasAlternateAmmo ( item->giTag ) ) + { + if ( ps->ammo[weaponData[item->giTag].attack[ATTACK_ALTERNATE].ammoIndex] >= ammoData[weaponData[item->giTag].attack[ATTACK_ALTERNATE].ammoIndex].max ) + { + return qfalse; + } + } + else + { + return qfalse; + } + } + + return qtrue; + + case IT_AMMO: + + if ( ps->ammo[item->giTag] >= ammoData[item->giTag].max) + { + return qfalse; // can't hold any more + } + return qtrue; + + case IT_ARMOR: + + if ( ps->stats[STAT_ARMOR] >= MAX_ARMOR ) + { + return qfalse; + } + + return qtrue; + + case IT_HEALTH: + + if ( item->quantity == 5 || item->quantity == 100 ) + { + if ( ps->stats[STAT_HEALTH] >= MAX_HEALTH ) + { + return qfalse; + } + + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= MAX_HEALTH ) + { + return qfalse; + } + + return qtrue; + + case IT_GAMETYPE: + + // Can only have one of a given item. This is for when there are + // multiple items floating around with the same name + if ( ps->stats[STAT_GAMETYPE_ITEMS] & (1<giTag) ) + { + return qfalse; + } + + // If both teams are checked then its ok to pick it up + if ( (ent->eFlags & EF_BLUETEAM) && (ent->eFlags & EF_REDTEAM) ) + { + return qtrue; + } + + if ( (ent->eFlags & EF_BLUETEAM) && !(ps->persistant[PERS_TEAM]==TEAM_BLUE) ) + { + return qfalse; + } + + if ( (ent->eFlags & EF_REDTEAM) && !(ps->persistant[PERS_TEAM]==TEAM_RED) ) + { + return qfalse; + } + + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + break; + + case IT_BACKPACK: + { + int i; + + // Not full health then pick it up + if ( ps->stats[STAT_HEALTH] != MAX_HEALTH ) + { + return qtrue; + } + + // Not full armor and dont have goggles then pick it up + if ( !ps->stats[STAT_GOGGLES] && ps->stats[STAT_ARMOR] != MAX_HEALTH ) + { + return qtrue; + } + + for ( i = 0; i < MAX_AMMO; i ++ ) + { + int maxammo; + + maxammo = BG_GetMaxAmmo ( ps, i ); + + if ( !maxammo || ps->ammo[i] >= maxammo ) + { + continue; + } + + return qtrue; + } + + // Grenades are a special case because they can be depleted by just throwing them. + // Therefore we need to check to see if they have any of the grenades indicated in + // their outfitting + if ( ps->pm_flags & PMF_LIMITED_INVENTORY ) + { + // No grenades, then let the pick up the backpack + if ( !(ps->stats[STAT_WEAPONS] & (1<stats[STAT_OUTFIT_GRENADE])) ) + { + return qtrue; + } + } + + return qfalse; + } + + default: +#ifndef Q3_VM +#ifndef NDEBUG + Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); +#endif +#endif + break; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_LIGHTGRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.3f * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + case TR_HEAVYGRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 1.0 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_LIGHTGRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= 0.6f * DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + case TR_HEAVYGRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= 2.0f * DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_FOOTSTEP", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + + "EV_JUMP", + "EV_WATER_FOOTSTEP", + "EV_WATER_TOUCH", // foot touches + + "EV_ITEM_PICKUP", // normal item pickups are predictable + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_ALT_FIRE", + + "EV_USE", // +Use key + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_PLAY_EFFECT", + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_ENTITY_SOUND", + + "EV_GLASS_SHATTER", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_BULLET", // otherEntity is the shooter + + "EV_PAIN", + "EV_OBITUARY", + + "EV_DESTROY_GHOUL2_INSTANCE", + + "EV_WEAPON_CHARGE", + "EV_WEAPON_CHARGE_ALT", + + "EV_DEBUG_LINE", + "EV_TESTLINE", + "EV_STOPLOOPINGSOUND", +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifdef _DEBUG + { + char buf[256]; + trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); + if ( atof(buf) != 0 ) { +#ifdef QAGAME + Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#else + Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#endif + } + } +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) +{ + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) + { + s->eType = ET_INVISIBLE; + } + else + { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) + { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) + { + SnapVector( s->apos.trBase ); + } + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->torsoTimer = ps->torsoTimer; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + + if ( ps->stats[STAT_HEALTH] <= 0 || ps->pm_type == PM_DEAD ) + { + s->eFlags |= EF_DEAD; + } + else + { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->pm_flags & PMF_GOGGLES_ON ) + { + s->eFlags |= EF_GOGGLES; + } + else + { + s->eFlags &= (~EF_GOGGLES); + } + + if ( ps->pm_flags & PMF_DUCKED) + { + s->eFlags |= EF_DUCKED; + } + else + { + s->eFlags &= (~EF_DUCKED); + } + + if ( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if ( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) + { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + s->gametypeitems = ps->stats[STAT_GAMETYPE_ITEMS]; + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + s->leanOffset = (int) BG_CalculateLeanOffset ( ps->leanTime ) + LEAN_OFFSET; + s->time = ps->persistant[PERS_SPAWN_COUNT]; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) +{ + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) + { + s->eType = ET_INVISIBLE; + } + else + { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) + { + SnapVector( s->pos.trBase ); + } + + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) + { + SnapVector( s->apos.trBase ); + } + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->torsoTimer = ps->torsoTimer; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + + if ( ps->stats[STAT_HEALTH] <= 0 || ps->pm_type == PM_DEAD ) + { + s->eFlags |= EF_DEAD; + } + else + { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->pm_flags & PMF_GOGGLES_ON ) + { + s->eFlags |= EF_GOGGLES; + } + else + { + s->eFlags &= (~EF_GOGGLES); + } + + if ( ps->pm_flags & PMF_DUCKED) + { + s->eFlags |= EF_DUCKED; + } + else + { + s->eFlags &= (~EF_DUCKED); + } + + if ( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if ( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) + { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + // Direct copies + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + s->gametypeitems = ps->stats[STAT_GAMETYPE_ITEMS]; + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + s->leanOffset = (int) BG_CalculateLeanOffset ( ps->leanTime ) + LEAN_OFFSET; + s->time = ps->persistant[PERS_SPAWN_COUNT]; +} + diff --git a/code/game/bg_player.c b/code/game/bg_player.c new file mode 100644 index 0000000..9bfcb3c --- /dev/null +++ b/code/game/bg_player.c @@ -0,0 +1,1741 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// BG_Player.c + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +#include "..\ghoul2\g2.h" + +#include "../cgame/animtable.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +TCharacterTemplate *bg_characterTemplates = NULL; +TItemTemplate *bg_itemTemplates = NULL; +int bg_identityCount = 0; +TIdentity bg_identities[MAX_IDENTITIES]; + +goutfitting_t bg_outfittings[MAX_OUTFITTINGS]; +int bg_outfittingCount = 0; + +char bg_availableOutfitting[WP_NUM_WEAPONS] = {-1}; + +int bg_outfittingGroups[OUTFITTING_GROUP_MAX][MAX_OUTFITTING_GROUPITEM] = +{ + { MODELINDEX_WEAPON_AK74, MODELINDEX_WEAPON_M4, MODELINDEX_WEAPON_USAS12, MODELINDEX_WEAPON_MSG90A1, MODELINDEX_WEAPON_M60, MODELINDEX_WEAPON_RPG7, MODELINDEX_WEAPON_MM1, -1, -1, -1 }, + { MODELINDEX_WEAPON_M590, MODELINDEX_WEAPON_MICROUZI, MODELINDEX_WEAPON_M3A1, -1, -1, -1, -1, -1, -1, - 1 }, + { MODELINDEX_WEAPON_M19, MODELINDEX_WEAPON_SOCOM, -1, -1, -1, -1, -1, -1, -1, -1 }, + { MODELINDEX_WEAPON_SMOHG92, MODELINDEX_WEAPON_M84, MODELINDEX_WEAPON_M15, MODELINDEX_WEAPON_ANM14, -1, -1, -1, -1, -1, -1 }, + { MODELINDEX_ARMOR, MODELINDEX_NIGHTVISION, MODELINDEX_THERMAL, -1, -1, -1, -1, -1, -1, -1 }, +}; + +/* +=================== +PM_StartLegsAnim + +Starts a new leg animation for the given playerstate +=================== +*/ +static void PM_StartLegsAnim( playerState_t* ps, int anim ) +{ + if ( ps->pm_type >= PM_DEAD ) + { + return; + } + + ps->legsAnim = ( ( ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + +/* +=================== +PM_ContinueLegsAnim + +Continues running the given leg animation, if its a new animation then it is started +=================== +*/ +void PM_ContinueLegsAnim( playerState_t* ps, int anim ) +{ + if ( ( ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) + { + return; + } + + PM_StartLegsAnim( ps, anim ); +} + +/* +=================== +PM_ForceLegsAnim +=================== +*/ +void PM_ForceLegsAnim( playerState_t* ps, int anim) +{ + PM_StartLegsAnim( ps, anim ); +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +void PM_StartTorsoAnim( playerState_t* ps, int anim, int time ) +{ + if ( anim == -1 ) + { + return; + } + + if ( ps->pm_type >= PM_DEAD ) + { + return; + } + + ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + ps->torsoTimer = time; +} + +/* +=================== +PM_ContinueTorsoAnim + +Continues running the given torso animation, if its a new animation then it is started +=================== +*/ +static void PM_ContinueTorsoAnim( playerState_t* ps, int anim ) +{ + if ( anim == -1 ) + { + return; + } + + if ( ( ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) + { + return; + } + + PM_StartTorsoAnim( ps, anim, 0 ); +} + +/* +============== +PM_TorsoAnimation + +Sets the current torso animation based on the given playerstate +============== +*/ +void PM_TorsoAnimation( playerState_t* ps ) +{ + switch ( ps->weaponstate ) + { + case WEAPON_SPAWNING: + case WEAPON_READY: + if ( (ps->pm_flags & PMF_ZOOMED) && weaponData[ps->weapon].animIdleZoomed ) + { + PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdleZoomed ); + } + else + { + PM_ContinueTorsoAnim ( ps, weaponData[ps->weapon].animIdle ); + } + + break; + } +} + +/* +================= +BG_ParseInventory + +Parses the inventory items indicated in the given group and returns +a linked list containing the results +================= +*/ +TInventoryTemplate *BG_ParseInventory ( TGPGroup group ) +{ + TInventoryTemplate *top, *inv; + TGPGroup subGroup; + char temp[1024]; + + // Convienience for handling no items + if (!group) + { + return 0; + } + + top = NULL; + + // Parse each of the inventory items + subGroup = trap_GPG_GetSubGroups( group ); + while(subGroup) + { + trap_GPG_GetName ( subGroup, temp ); + + // If the group name isnt item then 'Item' then its not an inventory item + if ( Q_stricmp ( temp, "Item") == 0) + { + inv = (TInventoryTemplate *)trap_VM_LocalAlloc ( sizeof(*inv)); + + // Name of the item + trap_GPG_FindPairValue ( subGroup, "Name||Name1", "", temp ); + inv->mName = trap_VM_LocalStringAlloc ( temp ); + + // Bolt for the item + trap_GPG_FindPairValue ( subGroup, "Bolt", "", temp ); + inv->mBolt = trap_VM_LocalStringAlloc ( temp ); + + trap_GPG_FindPairValue ( subGroup, "mp_onback||onback", "no", temp ); + if ( !Q_stricmp ( temp, "yes" ) ) + { + inv->mOnBack = qtrue; + } + + inv->mNext = top; + top = inv; + } + + // Move to the next group + subGroup = trap_GPG_GetNext ( subGroup ); + } + + return top; +} + +/* +================= +BG_ParseSkins + +Parses the skins contained in the given group and returns a linked +list with the results +================= +*/ +TSkinTemplate *BG_ParseSkins( TCharacterTemplate* character, TGPGroup group ) +{ + TSkinTemplate *skin; + TIdentity *identity; + TGPGroup subGroup; + char temp[1024]; + fileHandle_t f; +#ifndef SPECIAL_PRE_CACHE + int len; +#endif + qboolean validSkin; + + character->mSkins = NULL; + + // Parse the skin file first + trap_GPG_FindPairValue ( group, "SkinFile", "", temp ); + if ( temp[0] ) + { + skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) ); + + skin->mSkin = trap_VM_LocalStringAlloc ( temp ); + skin->mNext = character->mSkins; + character->mSkins = skin; + } + + // Now parse all the skin groups + subGroup = trap_GPG_GetSubGroups ( group ); + while(subGroup) + { + trap_GPG_GetName ( subGroup, temp ); + + // If the groups name isnt 'Skin' then skip it + if ( Q_stricmp( temp, "Skin") == 0 ) + { + // Allocate memory for the skin + skin = (TSkinTemplate *) trap_VM_LocalAlloc ( sizeof(*skin) ); + + // Grab the skin filename + trap_GPG_FindPairValue ( subGroup, "File", "", temp ); + skin->mSkin = trap_VM_LocalStringAlloc ( temp ); + +#ifdef SPECIAL_PRE_CACHE + f = 0; + validSkin = qtrue; +#else + Com_sprintf( temp, sizeof(temp), "models/characters/skins/%s.g2skin", skin->mSkin ); + len = trap_FS_FOpenFile( temp, &f, FS_READ ); + if (f != 0) + { + trap_FS_FCloseFile(f); + validSkin = qtrue; + } + else + { + validSkin = qfalse; + } +#endif + + // Parse the inventory for the skin + skin->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup ( subGroup, "Inventory") ); + + // Link the skin into the skin linked list + skin->mNext = character->mSkins; + character->mSkins = skin; + + // If the character isnt deathmatch then dont add it to the + // identity list + if ( character->mDeathmatch && validSkin) + { + // Allocate a new identity + identity = &bg_identities[bg_identityCount++]; + + identity->mCharacter = character; + identity->mSkin = skin; + + trap_GPG_FindPairValue ( subGroup, "mp_identity", "", temp ); + if ( !temp[0] ) + { + identity->mName = trap_VM_LocalStringAlloc ( va("%s/%s", character->mName, skin->mSkin ) ); + } + else + { + identity->mName = trap_VM_LocalStringAlloc ( temp ); + } + + // Team name? + trap_GPG_FindPairValue ( subGroup, "mp_team", "", temp ); + if ( temp[0] ) + { + identity->mTeam = trap_VM_LocalStringAlloc ( temp ); + } + else + { + identity->mTeam = ""; + } + } + } + + // Move to the next sub group in the parsers list + subGroup = trap_GPG_GetNext ( subGroup ); + } + + return character->mSkins; +} + +/* +================= +BG_ParseModelSounds + +Parses the model sounds for the given group and returns a linked +list with the results +================= +*/ +TModelSounds *BG_ParseModelSounds( TGPGroup group ) +{ + TModelSounds *top; + TModelSounds *sounds; + TGPGroup pairs; + char temp[1024]; + + // Convienience + if ( !group ) + { + return NULL; + } + + top = NULL; + + // Now parse all the skin groups + pairs = trap_GPG_GetPairs ( group ); + while(pairs) + { + // Allocate memory for the sounds + sounds = (TModelSounds*) trap_VM_LocalAlloc ( sizeof(*sounds) ); + sounds->mNext = top; + top = sounds; + + // Grab the sounds name + trap_GPV_GetName ( pairs, temp ); + sounds->mName = trap_VM_LocalStringAlloc ( temp ); + + // Start with no sounds + sounds->mCount = 0; + + // Should be a list + if ( trap_GPV_IsList ( pairs ) ) + { + TGPValue list; + + // Run through the list + list = trap_GPV_GetList ( pairs ); + while ( list && sounds->mCount < MAX_MODEL_SOUNDS ) + { + // Add the sound to the list + trap_GPV_GetName ( list, temp ); + sounds->mSounds[sounds->mCount++] = trap_VM_LocalStringAlloc ( temp ); + + list = trap_GPV_GetNext ( list ); + } + } + + // Move to the next sound set in the parsers list + pairs = trap_GPV_GetNext ( pairs ); + } + + return top; +} + +/* +================= +BG_ParseItemFile + +Parses the item file. The item file contains a list of all of the +items that can be used as inventory items for a given skin +================= +*/ +qboolean BG_ParseItemFile ( void ) +{ + TGPGroup *baseGroup, *subGroup; + TGPValue *pairs; + TItemTemplate *item; + TSurfaceList *surf; + char temp[1024]; + TGenericParser2 ItemFile; + + // Create the generic parser so the item file can be parsed + ItemFile = trap_GP_ParseFile( "ext_data/sof2.item", qtrue, qfalse ); + if ( !ItemFile ) + { + return qfalse; + } + + baseGroup = trap_GP_GetBaseParseGroup ( ItemFile ); + subGroup = trap_GPG_GetSubGroups ( baseGroup ); + while(subGroup) + { + trap_GPG_GetName ( subGroup, temp ); + + if (Q_stricmp( temp, "item") == 0) + { + // Is this item used for deathmatch? + trap_GPG_FindPairValue ( subGroup, "Deathmatch", "yes", temp ); + if (Q_stricmp( temp, "no") == 0) + { + subGroup = trap_GPG_GetNext ( subGroup ); + continue; + } + + // Allocate the item template and link it up to the item list + item = (TItemTemplate *) trap_VM_LocalAlloc ( sizeof(*item) ); + item->mNext = bg_itemTemplates; + bg_itemTemplates = item; + + // Name of the item + trap_GPG_FindPairValue ( subGroup, "Name", "", temp ); + item->mName = trap_VM_LocalStringAlloc ( temp ); + + // Model for the item + trap_GPG_FindPairValue ( subGroup, "Model", "", temp ); + if ( temp[0] ) + { + item->mModel = trap_VM_LocalStringAlloc ( temp ); + } + + pairs = trap_GPG_GetPairs ( subGroup ); + while(pairs) + { + trap_GPV_GetName ( pairs, temp ); + + // Surface off? + if ( Q_stricmpn ( temp, "offsurf", 7) == 0) + { + // Allocate the surface structure and link it into the list + surf = (TSurfaceList *) trap_VM_LocalAlloc ( sizeof(*surf) ); + surf->mNext = item->mOffList; + item->mOffList = surf; + + // Name of the surface to turn off + trap_GPV_GetTopValue ( pairs, temp ); + surf->mName = trap_VM_LocalStringAlloc ( temp ); + } + // Surface on? + else if ( Q_stricmpn( temp, "onsurf", 6) == 0) + { + // Allocate the surface structure and link it into the list + surf = (TSurfaceList *) trap_VM_LocalAlloc(sizeof(*surf)); + surf->mNext = item->mOnList; + item->mOnList = surf; + + // Name of the surface to turn off + trap_GPV_GetTopValue ( pairs, temp ); + surf->mName = trap_VM_LocalStringAlloc ( temp ); + } + + // Next pairs + pairs = trap_GPV_GetNext ( pairs ); + } + } + + // Next group + subGroup = trap_GPG_GetNext ( subGroup ); + } + + trap_GP_Delete(&ItemFile); + + return qtrue; +} + +/* +================= +BG_FindCharacterTemplate + +Finds a character template the matches the given name or NULL if +a match could not be found +================= +*/ +TCharacterTemplate *BG_FindCharacterTemplate (const char *name) +{ + TCharacterTemplate *current; + + // Convienience + if (!name) + { + return NULL; + } + + // Linear search through all of the parsed templates + current = bg_characterTemplates; + while(current) + { + if (Q_stricmp(name, current->mName) == 0) + { + return current; + } + + current = current->mNext; + } + + // None found + return NULL; +} + +/* +================= +BG_FindItemTemplate + +Finds an item template the matches the given name or NULL if a match +could not be found +================= +*/ +TItemTemplate *BG_FindItemTemplate(const char *name) +{ + TItemTemplate *current; + + // Convienience + if (!name) + { + return NULL; + } + + // Linear search through all of the parsed items + current = bg_itemTemplates; + while(current) + { + if (Q_stricmp(name, current->mName) == 0) + { + return current; + } + + current = current->mNext; + } + + return NULL; +} + +/* +================= +BG_LinkTemplates + +Cross links the various templates +================= +*/ +static void BG_LinkTemplates(void) +{ + TCharacterTemplate *current; + TInventoryTemplate *inv; + TSkinTemplate *skin; + + current = bg_characterTemplates; + while(current) + { + // If this template has a parent then find it and link it up as + // its parent. Ensure that the parent doesnt link back to itself + current->mParent = BG_FindCharacterTemplate(current->mParentName); + if (current->mParent == current) + { + current->mParent = NULL; + } + // Bring over any parent items + else if ( current->mParent ) + { + // No model, bring over the parents. + if ( !current->mModel ) + { + current->mModel = current->mParent->mModel; + } + } + + // Link up all the inventory for this character + inv = current->mInventory; + while(inv) + { + inv->mItem = BG_FindItemTemplate(inv->mName); + inv = inv->mNext; + } + + // Link up all the skins for this character + skin = current->mSkins; + while(skin) + { + // Link up all the inventory items specific to the skins + inv = skin->mInventory; + while(inv) + { + inv->mItem = BG_FindItemTemplate(inv->mName); + + inv = inv->mNext; + } + + skin = skin->mNext; + } + + // Move on to the next character template + current = current->mNext; + } +} + +/* +================= +BG_ParseNPCFiles + +Parses all the the .npc files in the npc directory and +stores their info into global lists. +================= +*/ +qboolean BG_ParseNPCFiles ( void ) +{ + int i, numNPCFiles, filelen; + TGPGroup baseGroup, subGroup; + TGenericParser2 NPCFile; + const char *currentParent = 0; + TCharacterTemplate *newTemplate; + char fileName[MAX_QPATH]; + char NPCFiles[4096]; + char temp[1024]; + char *fileptr; + + // Clear the current list + bg_characterTemplates = NULL; + + // Grab the list of NPC files + numNPCFiles = trap_FS_GetFileList("NPCs", ".npc", NPCFiles, 4096 ); + if ( !numNPCFiles ) + { + return qfalse; + } + + // Parse each of the NPC files + fileptr = NPCFiles; + for( i=0; imNext = bg_characterTemplates; + bg_characterTemplates = newTemplate; + + // Exclude from deathmatch? + trap_GPG_FindPairValue ( subGroup, "DeathMatch", "yes", temp ); + if ( Q_stricmp( temp, "no") == 0) + { + newTemplate->mDeathmatch = qfalse; + } + else + { + newTemplate->mDeathmatch = qtrue; + } + + // Template name + trap_GPG_FindPairValue ( subGroup, "Name", "", temp ); + if ( temp[0] ) + { + newTemplate->mName = trap_VM_LocalStringAlloc ( temp ); + } + + // Template formal name + trap_GPG_FindPairValue ( subGroup, "FormalName", "", temp ); + if ( temp[0] ) + { + newTemplate->mFormalName = trap_VM_LocalStringAlloc ( temp ); + } + + // Template model + trap_GPG_FindPairValue ( subGroup, "Model", "", temp ); + if ( temp[0] ) + { + newTemplate->mModel = trap_VM_LocalStringAlloc ( temp ); + } + + // Use the current parent + newTemplate->mParentName = currentParent; + + // Parse inventory for this character template + newTemplate->mInventory = BG_ParseInventory( trap_GPG_FindSubGroup( subGroup, "Inventory" )); + + // Parse the skins for this character template + BG_ParseSkins( newTemplate, subGroup); + + // Parse the sounds for this character template + newTemplate->mSounds = BG_ParseModelSounds ( trap_GPG_FindSubGroup ( subGroup, "MPSounds" ) ); + } + + // Move to the next group + subGroup = trap_GPG_GetNext ( subGroup ); + } + + trap_GP_Delete(&NPCFile); + } + + // Parse the item file + BG_ParseItemFile(); + + // Link up all the templates + BG_LinkTemplates(); + + return qtrue; +} + +/* +================= +BG_GetModelSoundsGroup + +Returns the group of sounds for the given model and sound group combination, if the +sound group couldnt be found NULL is returned +================= +*/ +TModelSounds* BG_GetModelSoundsGroup ( const char* Identity, const char* SoundGroup ) +{ + TIdentity *identity; + TCharacterTemplate *character; + TModelSounds *sounds; + + // Grab the identity in question + identity = BG_FindIdentity (Identity ); + if ( !identity ) + { + return NULL; + } + + character = identity->mCharacter; + + while ( character ) + { + // Run through the sounds and look for the match + sounds = character->mSounds; + while ( sounds ) + { + // Match? + if ( !Q_stricmp ( sounds->mName, SoundGroup ) ) + { + return sounds; + } + + sounds = sounds->mNext; + } + + character = character->mParent; + } + + // Not found + return NULL; +} + +/* +================= +BG_GetModelSoundCount + +Return the number of sounds for the given model +================= +*/ +int BG_GetModelSoundCount ( const char *Identity, const char *SoundGroup ) +{ + TModelSounds* sounds; + + // Grab the sounds + sounds = BG_GetModelSoundsGroup( Identity, SoundGroup ); + if ( !sounds ) + { + return 0; + } + + // Return the sound count + return sounds->mCount; +} + +/* +================= +BG_GetModelSound + +Returns the model sound for the given sound group and index. If the sound +could not be found then NULL is returned. +================= +*/ +const char *BG_GetModelSound ( const char *Identity, const char *SoundGroup, int index ) +{ + TModelSounds *sounds; + + // Grab the sounds + sounds = BG_GetModelSoundsGroup( Identity, SoundGroup ); + if ( !sounds ) + { + return NULL; + } + + // Invalid index? + if ( index >= sounds->mCount ) + { + return ""; + } + + // Run through the sounds and look for the match + return sounds->mSounds[index]; +} + +/* +================= +BG_FindIdentity + +Returns the identity with the given name +================= +*/ +TIdentity *BG_FindIdentity ( const char *identityName ) +{ + int i; + + // Convienience + if (!identityName) + { + return NULL; + } + + // Linear search through all of the parsed identities + for ( i = 0; i < bg_identityCount; i ++ ) + { + if (Q_stricmp(identityName, bg_identities[i].mName) == 0) + { + return &bg_identities[i]; + } + } + + // None found + return NULL; +} + +/* +================= +BG_FindTeamIdentity + +returns the first identity which matches the given team name +================= +*/ +#define MAX_TEAM_IDENTS 5 +TIdentity* BG_FindTeamIdentity ( const char* teamName, int index ) +{ + TIdentity* idents[MAX_TEAM_IDENTS]; + int count; + int i; + + // Convienience + if ( !teamName ) + { + return NULL; + } + + // Linear search through all of the parsed identities + for ( i = 0, count = 0; i < bg_identityCount && count < MAX_TEAM_IDENTS; i ++ ) + { + if (Q_stricmp(teamName, bg_identities[i].mTeam) == 0) + { + idents[count++] = &bg_identities[i]; + } + } + + if ( !count ) + { + return NULL; + } + + if ( index != -1 ) + { + if ( index >= count ) + { + index = count - 1; + } + + return idents[index]; + } + + // None found + return idents[rand()%count]; +} + + +/* +================= +BG_ParseSkin + +Reads a skin file into a null terminated list and returns the number found +================= +*/ +int BG_ParseSkin ( const char* filename, char* pairs, int pairsSize ) +{ + TGenericParser2 skinFile; + TGPGroup *basegroup; + TGPGroup *group; + TGPGroup *sub; + char name[MAX_QPATH]; + char* end; + int numPairs; + + // Open the skin file + skinFile = trap_GP_ParseFile( (char*)filename, qtrue, qfalse ); + if ( !skinFile ) + { + return 0; + } + + numPairs = 0; + end = pairs; + *end = 0; + basegroup = trap_GP_GetBaseParseGroup ( skinFile ); + group = trap_GPG_GetSubGroups ( basegroup ); + + while(group) + { + trap_GPG_GetName ( group, name ); + + // Parse the material + if ( Q_stricmp ( name, "material") == 0) + { + char matName[MAX_QPATH]; + char shaderName[MAX_QPATH]; + + trap_GPG_FindPairValue ( group, "name", "", matName ); + + sub = trap_GPG_FindSubGroup ( group, "group"); + if (sub) + { + trap_GPG_FindPairValue ( sub, "shader1", "", shaderName ); + if (!shaderName[0]) + { + trap_GPG_FindPairValue ( sub, "texture1", "", shaderName ); + } + } + + if (matName[0] && shaderName[0]) + { + int size; + + size = Com_sprintf(end, pairsSize, "%s %s ", matName, shaderName); + end += size; + pairsSize -= size; + numPairs++; + } + } + + group = trap_GPG_GetNext ( group ); + } + + trap_GP_Delete(&skinFile); + + return numPairs; +} + +/* +================== +BG_SwingAngles +================== +*/ +static void BG_SwingAngles ( + float destination, + float swingTolerance, + float clampTolerance, + float speed, + float *angle, + qboolean *swinging, + int frameTime + ) +{ + float swing; + float move; + float scale; + + if ( !*swinging ) + { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) + { + *swinging = qtrue; + } + } + + if ( !*swinging ) + { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) + { + scale = 0.5; + } + else if ( scale < swingTolerance ) + { + scale = 1.0; + } + else + { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) + { + move = frameTime * scale * speed; + if ( move >= swing ) + { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + else if ( swing < 0 ) + { + move = frameTime * scale * -speed; + if ( move <= swing ) + { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) + { + *angle = AngleMod( destination - (clampTolerance - 1) ); + } + else if ( swing < -clampTolerance ) + { + *angle = AngleMod( destination + (clampTolerance - 1) ); + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +#define PAIN_TWITCH_TIME 200 +static void BG_AddPainTwitch( int painTime, int painDirection, int currentTime, vec3_t torsoAngles ) { + int t; + float f; + + t = currentTime - painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + +/* +================= +BG_CalculateLeanOffset +================= +*/ +float BG_CalculateLeanOffset ( int leanTime ) +{ + return ((float)(leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET); +} + +/* +================= +BG_PlayerAngles +================= +*/ +void BG_PlayerAngles ( + + vec3_t startAngles, + vec3_t legs[3], + + vec3_t legsAngles, // out + vec3_t lowerTorsoAngles, + vec3_t upperTorsoAngles, + vec3_t headAngles, + + int leanOffset, + + int painTime, + int painDirection, + int currentTime, + + animInfo_t* torsoInfo, + animInfo_t* legsInfo, + + int frameTime, + vec3_t realvelocity, + qboolean dead, + float movementDir, + void* ghoul2 + ) +{ + float dest; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + float speed; + int dir; + vec3_t velocity; + + VectorCopy( startAngles, headAngles ); + VectorCopy ( realvelocity, velocity ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( lowerTorsoAngles ); + VectorClear( upperTorsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( legsInfo->anim & ~ANIM_TOGGLEBIT ) != TORSO_IDLE_PISTOL ) + { + // if not standing still, always point all in the same direction + torsoInfo->yawing = qtrue; + torsoInfo->pitching = qtrue; + legsInfo->yawing = qtrue; + } + + speed = VectorNormalize( velocity ); + + // adjust legs for movement dir + if (dead) + { + dir = 0; + } + else + { + dir = movementDir; + + if ( leanOffset && !speed ) + { + dir = 0; + } + } + +// legsAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ]; +// torsoAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ]; + legsAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ]; + lowerTorsoAngles[YAW] = headAngles[YAW] + 2 * movementOffsets[ dir ]; + + // torso + BG_SwingAngles( lowerTorsoAngles[YAW], 25, 90, 0.3f, &torsoInfo->yawAngle, &torsoInfo->yawing, frameTime ); + BG_SwingAngles( legsAngles[YAW], 40, 90, 0.3f, &legsInfo->yawAngle, &legsInfo->yawing, frameTime ); + + if ( leanOffset ) + { + legsAngles[YAW] = headAngles[YAW]; + } + else + { + legsAngles[YAW] = legsInfo->yawAngle; + } + + lowerTorsoAngles[YAW] = Com_Clampf ( -90, 90, AngleDelta (headAngles[YAW], legsAngles[YAW] ) ); + upperTorsoAngles[YAW] = lowerTorsoAngles[YAW] / 2; + lowerTorsoAngles[YAW] = legsAngles[YAW] + lowerTorsoAngles[YAW] / 2; + + headAngles[YAW] -= upperTorsoAngles[YAW]; + + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75; + } else { + dest = headAngles[PITCH] * 0.75; + } + + lowerTorsoAngles[PITCH] = dest; + + // --------- roll ------------- + + // lean towards the direction of travel + + // Add in leanoffset + if ( leanOffset ) + { + lowerTorsoAngles[ROLL] -= ((float)leanOffset * 1.25f); + lowerTorsoAngles[YAW] -= 1.25f * ((float)leanOffset/LEAN_OFFSET) * dest; + headAngles[YAW] -= ((float)leanOffset/LEAN_OFFSET) * dest; + headAngles[ROLL] -= ((float)leanOffset * 1.25f); + } + else if ( speed ) + { + vec3_t axis[3]; + float side; + + speed *= 0.025; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + } + + // pain twitch + BG_AddPainTwitch( painTime, painDirection, currentTime, lowerTorsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, lowerTorsoAngles, headAngles ); + AnglesSubtract( lowerTorsoAngles, legsAngles, lowerTorsoAngles ); + + if ( legs ) + { + AnglesToAxis( legsAngles, legs ); + + // Apply the rotations + trap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", upperTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime); + + trap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", lowerTorsoAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, currentTime); + trap_G2API_SetBoneAngles(ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, 0,0, currentTime); + } +} + +/* +====================== +BG_ParseAnimationFile + +Read a configuration file containing animation counts and rates +models/players/visor/animation.cfg, etc +====================== +*/ +qboolean BG_ParseAnimationFile ( const char *filename, animation_t* animations ) +{ + const char *text_p; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + int animNum; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 || len >= sizeof( text ) - 1 ) + { + return qfalse; + } + + trap_FS_Read( text, len, f ); + trap_FS_FCloseFile( f ); + + // parse the text + text[len] = 0; + text_p = text; + skip = 0; + + //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 + for(i = 0; i < MAX_ANIMATIONS; i++) + { + animations[i].firstFrame = 0; + animations[i].numFrames = 0; + animations[i].loopFrames = -1; + animations[i].frameLerp = 100; + animations[i].initialLerp = 100; + } + + // read information for each frame + while(1) + { + token = COM_Parse( &text_p ); + + if ( !token || !token[0]) + { + break; + } + + animNum = GetIDForString(bg_animTable, token); + if(animNum == -1) + { +//#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); +#endif + continue; + } + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].numFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + animations[animNum].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animations[animNum].frameLerp = floor(1000.0f / fps); + } + else + { + animations[animNum].frameLerp = ceil(1000.0f / fps); + } + + animations[animNum].initialLerp = ceil(1000.0f / fabs(fps)); + } + + return qtrue; +} + +/* +======================== +BG_SetAvailableOutfitting + +Set the current availability table +======================== +*/ +void BG_SetAvailableOutfitting ( const char* available ) +{ + int len; + + len = strlen ( available ); + if ( len > WP_NUM_WEAPONS ) + { + len = WP_NUM_WEAPONS; + } + + // IF the availability has changed force a reload of the outfitting groups + if ( Q_strncmp ( available, bg_availableOutfitting, min(sizeof(bg_availableOutfitting),len) ) ) + { + bg_outfittingCount = 0; + } + + // Initialize it to all on. + memset ( &bg_availableOutfitting[0], '2', sizeof(bg_availableOutfitting) ); + memcpy ( &bg_availableOutfitting[0], available, len ); +} + +/* +======================== +BG_IsWeaponAvailableForOutfitting + +Is the given weapon available for outfitting? +======================== +*/ +qboolean BG_IsWeaponAvailableForOutfitting ( weapon_t weapon, int level ) +{ + if ( bg_availableOutfitting[0] == -1 ) + { + return qtrue; + } + + if ( bg_availableOutfitting[weapon-1] - '0' >= level ) + { + return qtrue; + } + + return qfalse; +} + +/* +======================== +BG_DecompressOutfitting + +Decompresses the given outfitting string into the outfitting structure +======================== +*/ +void BG_DecompressOutfitting ( const char* compressed, goutfitting_t* outfitting) +{ + int group; + int origitem; + + memset ( outfitting->items, 0, sizeof(outfitting->items) ); + + for ( group = 0; group < OUTFITTING_GROUP_MAX; group ++ ) + { + int item; + + if ( !*compressed ) + { + item = -1; + } + else + { + item = ((*compressed++) - 'A'); + } + + // Valid item number? + if ( item < 0 || item >= 10 ) + { + item = 0; + } + + // Valid slot for the group ? + if ( bg_outfittingGroups[group][item] == -1 ) + { + continue; + } + + // Ok to set the item now + outfitting->items[group] = item; + + // No initialized + if ( bg_availableOutfitting[0] == -1 ) + { + continue; + } + + // Is it available? + if ( bg_itemlist[bg_outfittingGroups[group][item]].giType == IT_WEAPON ) + { + origitem = item; + while ( !BG_IsWeaponAvailableForOutfitting ( bg_itemlist[bg_outfittingGroups[group][item]].giTag, 2 ) ) + { + item++; + if ( bg_outfittingGroups[group][item] == -1 ) + { + item = 0; + } + + if ( item == origitem ) + { + //Com_Error ( ERR_FATAL, "ERROR: There must be at least one weapon available in each category" ); + item = -1; + break; + } + } + } + + // Ok to set the item now + outfitting->items[group] = item; + } +} + +/* +======================== +BG_CompressOutfitting + +Compresses the given outfitting structure into the given string +======================== +*/ +void BG_CompressOutfitting ( goutfitting_t* outfitting, char* compressed, int size ) +{ + int i; + + for ( i = 0; i < OUTFITTING_GROUP_MAX && size; i ++, size-- ) + { + *compressed++ = outfitting->items[i] + 'A'; + } + + *compressed = '\0'; +} + +/* +======================== +BG_FindOutfitting + +Finds the real outfitting that matches the given outfitting +======================== +*/ +int BG_FindOutfitting ( goutfitting_t* outfitting ) +{ + int i; + int l; + + // Loop through all the outfittings linearly + for ( i = 0; i < bg_outfittingCount; i ++ ) + { + for ( l = 0; l < OUTFITTING_GROUP_MAX; l ++ ) + { + if ( bg_outfittings[i].items[l] != outfitting->items[l] ) + { + break; + } + } + + // Both iterators at the end signifies a match + if ( l == OUTFITTING_GROUP_MAX ) + { + return i; + } + } + + return -1; +} + +/* +======================== +BG_ParseOutfittingTemplate + +Parses a single outfitting template +======================== +*/ +qboolean BG_ParseOutfittingTemplate ( const char* fileName, goutfitting_t* outfitting ) +{ + TGPGroup baseGroup; + TGPGroup subGroup; + TGenericParser2 file; + TGPGroup pairs; + char temp[MAX_OUTFITTING_NAME]; + + // Initialize the outfitting + memset ( outfitting, 0, sizeof(goutfitting_t) ); + + // Create the generic parser so the item file can be parsed + file = trap_GP_ParseFile( (char*)fileName, qtrue, qfalse ); + if ( !file ) + { + return qfalse; + } + + // Start at the top with the "outfitting" group + baseGroup = trap_GP_GetBaseParseGroup ( file ); + subGroup = trap_GPG_FindSubGroup ( baseGroup, "outfitting" ); + if ( !subGroup ) + { + trap_GP_Delete(&file); + return qfalse; + } + + // Get the name of the template + trap_GPG_FindPairValue ( subGroup, "displayName", fileName, outfitting->name ); + + // Sub group named "items" + pairs = trap_GPG_FindPair ( subGroup, "items" ); + if ( pairs ) + { + TGPValue list; + + // Run through the list + list = trap_GPV_GetList ( pairs ); + while ( list ) + { + gitem_t* item; + + trap_GPV_GetName ( list, temp ); + + item = BG_FindItem ( temp ); + if ( item ) + { + int index; + for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ ) + { + if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) ) + { + outfitting->items[item->outfittingGroup] = index; + break; + } + } + } + + list = trap_GPV_GetNext ( list ); + } + } + + // Sub group named "weapons" + pairs = trap_GPG_FindPair ( subGroup, "weapons" ); + + // Run through the weapons + if ( pairs ) + { + TGPValue list; + + // Run through the list + list = trap_GPV_GetList ( pairs ); + while ( list ) + { + gitem_t* item; + int i; + + trap_GPV_GetName ( list, temp ); + + // Lookup the weapon number + for( i = WP_NONE + 1, item=NULL; i < WP_NUM_WEAPONS; i++ ) + { + if ( Q_stricmp(bg_weaponNames[i], temp ) == 0) + { + // translate the weapon index into an item index. + item = BG_FindWeaponItem ( (weapon_t) i ); + break; + } + } + + // If the weapon translated into an item ok then drop it + // in its appropriate slot. + if ( item ) + { + int index; + + // Make sure outfitting groups that have weapons that are not available + // do not show up + if ( !BG_IsWeaponAvailableForOutfitting ( item->giTag, 2 ) ) + { + trap_GP_Delete(&file); + return qfalse; + } + + for ( index=0; bg_outfittingGroups[item->outfittingGroup][index] != -1; index ++ ) + { + if ( bg_outfittingGroups[item->outfittingGroup][index] == (item - &bg_itemlist[0]) ) + { + outfitting->items[item->outfittingGroup] = index; + break; + } + } + } + + list = trap_GPV_GetNext ( list ); + } + } + + trap_GP_Delete(&file); + + return qtrue; +} + +/* +======================== +BG_ParseOutfittingTemplates + +Parses the available outfitting templates +======================== +*/ +int BG_ParseOutfittingTemplates ( qboolean force ) +{ + int i; + int numOutfittingFiles; + int filelen; + char fileName[MAX_QPATH]; + char outfittingFiles[4096]; + char *fileptr; + + // Dont reload unless forced + if ( bg_outfittingCount && !force ) + { + return bg_outfittingCount; + } + + // Clear the current list + bg_outfittingCount = 1; + strcpy ( bg_outfittings[0].name, "CUSTOM" ); + + // Grab the list of NPC files + numOutfittingFiles = trap_FS_GetFileList("scripts", ".outfitting", outfittingFiles, 4096 ); + if ( !numOutfittingFiles ) + { + return 0; + } + + // Parse each of the NPC files + fileptr = outfittingFiles; + for( i = 0; i < numOutfittingFiles; i++, fileptr += filelen+1 ) + { + // Grab the length so we can skip this file later + filelen = strlen(fileptr); + + Com_sprintf ( fileName, sizeof(fileName), "scripts/%s", fileptr ); + + // Parse the outfitting template + if ( BG_ParseOutfittingTemplate ( fileName, &bg_outfittings[bg_outfittingCount] ) ) + { + bg_outfittingCount++; + } + } + + return bg_outfittingCount; +} + + diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c new file mode 100644 index 0000000..8d9056d --- /dev/null +++ b/code/game/bg_pmove.c @@ -0,0 +1,3901 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +pmove_t *pm; +pml_t pml; + +// Speed scales +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.25f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; +const float pm_ladderScale = 0.5f; + +// Accelerations +float pm_accelerate = 6.0f; +float pm_airaccelerate = 1.0f; +float pm_wateraccelerate = 4.0f; +float pm_flyaccelerate = 8.0f; + +// Frictions +float pm_headfriction = 0.0f; // Friction when on someones head +float pm_friction = 6.0f; // Friction when on the ground +float pm_waterfriction = 3.0f; // Friction when in water +float pm_ladderfriction = 6.0f; // Friction when on a ladder +float pm_spectatorfriction = 5.0f; // Friction when flying aroudn as a spectator + +int c_pmove = 0; + +ladder_t pm_ladders[MAX_LADDERS]; +int pm_laddercount = 0; + +static void PM_Weapon_AddInaccuracy ( attackType_t attack ); +static void PM_Weapon_AddKickAngles ( vec3_t kickAngles ); +static void PM_BeginZoomOut ( void ); + +/* +=============== +PM_AddEvent +=============== +*/ +void PM_AddEvent( int newEvent ) +{ + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddEventWithParm +=============== +*/ +void PM_AddEventWithParm( int newEvent, int parm ) +{ + BG_AddPredictableEventToPlayerstate( newEvent, parm, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt ( int entityNum ) +{ + int i; + + // Cant add the world as a touch entity + if ( entityNum == ENTITYNUM_WORLD ) + { + return; + } + + // Ensure the max touch limit has not been reached + if ( pm->numtouch == MAXTOUCH ) + { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) + { + if ( pm->touchents[ i ] == entityNum ) + { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +{ + float backoff; + float change; + int i; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) + { + backoff *= overbounce; + } + else + { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) +{ + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) + { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength(vec); + if (speed < 1) + { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction + if ( pm->waterlevel <= 1 ) + { + if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) + { + // if getting knocked back, no friction + if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->ps->pm_flags & PMF_LADDER ) + { + if ( !pml.groundPlane ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_ladderfriction*pml.frametime; + } + } + else if ( pm->waterlevel ) + { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + // If on someones head then use special friction + else if ( pm->ps->groundEntityNum < MAX_CLIENTS ) + { + drop = speed*pm_headfriction*pml.frametime; + } + + if ( pm->ps->pm_type == PM_SPECTATOR) + { + drop += speed*pm_spectatorfriction*pml.frametime; + } + + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) +{ +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*pml.frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed*wishdir[i]; + } +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel*pml.frametime*wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) +{ + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) + { + max = abs( cmd->rightmove ); + } + + if ( abs( cmd->upmove ) > max ) + { + max = abs( cmd->upmove ); + } + + if ( !max ) + { + return 0; + } + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) +{ + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) + { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) + { + pm->ps->movementDir = 0; + } + else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) + { + pm->ps->movementDir = 1; + } + else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) + { + pm->ps->movementDir = 2; + } + else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) + { + pm->ps->movementDir = 3; + } + else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) + { + pm->ps->movementDir = 4; + } + else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) + { + pm->ps->movementDir = 5; + } + else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) + { + pm->ps->movementDir = 6; + } + else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) + { + pm->ps->movementDir = 7; + } + } + else + { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) + { + pm->ps->movementDir = 1; + } + else if ( pm->ps->movementDir == 6 ) + { + pm->ps->movementDir = 7; + } + } +} + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) +{ + if ( pm->ps->pm_time ) + { + return qfalse; + } + + // Cant jump when ducked + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + return qfalse; + } + + // don't allow jump until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) + { + return qfalse; + } + + // not holding jump + if ( pm->cmd.upmove < 10 ) + { + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_debounce & PMD_JUMP ) + { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_debounce |= PMD_JUMP; + pm->ps->pm_flags |= PMF_JUMPING; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + + if ( pm->cmd.forwardmove >= 0 ) + { + PM_ForceLegsAnim( pm->ps, LEGS_JUMP ); + } + else + { + PM_ForceLegsAnim( pm->ps, LEGS_JUMP_BACK ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + // Special case for ladders + if ( pm->ps->ladder != -1 ) + { + vec3_t forward; + VectorCopy ( pm_ladders[pm->ps->ladder].fwd, forward ); + forward[2] = 0; + VectorNormalize ( forward ); + VectorMA ( pm->ps->velocity, -50, forward, pm->ps->velocity ); + pm->ps->pm_flags |= PMF_LADDER_JUMP; + return qtrue; + } + + pm->ps->velocity[2] = JUMP_VELOCITY; + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) +{ + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) + { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) + { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) + { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( cont ) + { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, pm->ps->velocity); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) +{ + // waterjump has no control, but falls + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) + { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove +=================== +*/ +static void PM_WaterMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) + { + PM_WaterJumpMove(); + return; + } + +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) + { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + } + else + { + for (i=0 ; i<3 ; i++) + { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps->speed * pm_swimScale ) + { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) + { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + +/* +=================== +PM_FlyMove +=================== +*/ +static void PM_FlyMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) + { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } + else + { + for (i=0 ; i<3 ; i++) + { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + + PM_StepSlideMove ( qtrue ); +} + +/* +=================== +PM_WalkMove +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump () ) + { + PM_BeginZoomOut ( ); + + // jumped away + if ( pm->waterlevel > 1 ) + { + PM_WaterMove(); + } + else + { + PM_AirMove(); + } + return; + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel && !(pm->watertype & CONTENTS_LADDER) ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + { + accelerate = pm_airaccelerate; + } + else + { + accelerate = pm_accelerate; + + // Accelerate faster when ducked + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + accelerate *= 2; + } + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } + + vel = VectorLength(pm->ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + + // don't do anything if standing still + if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) + { + return; + } + + PM_StepSlideMove( qfalse ); +} + +/* +=================== +PM_LadderMove +=================== +*/ +static void PM_LadderMove ( void ) +{ + float wishspeed; + float scale; + vec3_t wishdir; + vec3_t wishvel; + float accelerate; + + if ( PM_CheckJump () ) + { + return; + } + + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + + accelerate = pm_accelerate; + + // + // user intentions + // + if ( !scale ) + { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } + else + { + int i; + + VectorNormalize ( pm_ladders[pm->ps->ladder].fwd ); + VectorNormalize ( pml.forward ); + + if ( !pml.groundPlane ) + { + vec3_t mins; + vec3_t maxs; + vec3_t offset = {1, 1, 1}; + trace_t tr; + + VectorCopy ( pm->mins, mins ); + VectorSubtract ( mins, offset, mins ); + VectorCopy ( pm->maxs, maxs ); + VectorAdd ( maxs, offset, maxs ); + + pm->trace ( &tr, pm->ps->origin, mins, maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( tr.fraction == 1.0f || !tr.startsolid ) + { + if ( pm->cmd.forwardmove >= 0 ) + { + VectorAdd ( pml.forward, pm_ladders[pm->ps->ladder].fwd, pml.forward ); + } + else + { + VectorSubtract ( pml.forward, pm_ladders[pm->ps->ladder].fwd, pml.forward ); + } + } + } + + for ( i=0 ; i<3 ; i++ ) + { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove * pm_ladderScale; + } + + // Duck down ladders + if ( pm->cmd.upmove < 0 ) + { + wishvel[2] += scale * pm->cmd.upmove; + } + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + PM_StepSlideMove( qfalse ); +} + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + scale *= 1.5f; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static void PM_FootstepForSurface( void ) +{ + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) + { + return; + } + + PM_AddEventWithParm(EV_FOOTSTEP, pml.groundTrace.surfaceFlags & MATERIAL_MASK); +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ + +int minDeltaForDmg = 97; +int minDeltaForSmallPainSound = 30; +int minDeltaForBigPainSound = 97; +int minDeltaForSlowDown = 17; + +static void PM_CrashLand( int impactMaterial, vec3_t impactNormal ) +{ + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + float f; + int scaleDelta; + qboolean jumped; + + static vec3_t up = {0,0,1}; + + // were they juping? + jumped = (pm->ps->pm_flags&PMF_JUMPING) ? qtrue : qfalse; + + pm->ps->pm_flags &= (~PMF_LADDER_JUMP); + pm->ps->pm_flags &= (~PMF_JUMPING); + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) + { + return; + } + + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta * delta * 0.000275f; + + switch ( pm->waterlevel ) + { + case 3: + // never take falling damage if completely underwater + return; + + // reduce falling damage if there is standing water + case 2: + delta *= 0.25; + break; + + // reduce falling damage if there is standing water + case 1: + delta *= 0.5; + break; + } + + // Scale the delta based on the normal of the plane we hit + f = DotProduct ( up, impactNormal ); + if ( f < .25 ) + { + delta *= f; + } + + // Just hit the ground, no more z velocity or we could bounce + pm->ps->velocity[2] = 0; + + // start footstep cycle over if it wasnt a little jump + if ( delta > minDeltaForSlowDown ) + { + pm->ps->bobCycle = 0; + } + + if ( delta < 1 ) + { + return; + } + else if ( jumped && delta >= minDeltaForSlowDown ) + { + // Cut their forward velocity, this pretty much eliminates strafe jumping + pm->ps->velocity[0] *= 0.25f; + pm->ps->velocity[1] *= 0.25f; + + pm->ps->pm_time = 500; + } + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + scaleDelta = (int)delta; + if (scaleDelta > 100 + minDeltaForDmg) + { + scaleDelta = 100 + minDeltaForDmg; + } + scaleDelta -= minDeltaForDmg; + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) + { + if ( delta > minDeltaForBigPainSound ) + { + PM_AddEventWithParm(EV_FALL_FAR, scaleDelta | ((impactMaterial & MATERIAL_MASK)<<8)); + } + else if ( delta > minDeltaForDmg ) + { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) + { + PM_AddEventWithParm(EV_FALL_MEDIUM, scaleDelta | ((impactMaterial & MATERIAL_MASK)<<8)); + } + } + else if ( delta > minDeltaForSlowDown ) + { + PM_AddEventWithParm(EV_FALL_SHORT, impactMaterial & MATERIAL_MASK ); + } + else if ( delta > 10 && (pm->cmd.buttons & BUTTON_WALKING) ) + { + PM_FootstepForSurface(); + } + } +} + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) +{ + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) + { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // jitter around + for (i = -1; i <= 1; i++) { + for (j = -1; j <= 1; j++) { + for (k = -1; k <= 1; k++) { + VectorCopy(pm->ps->origin, point); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) +{ + trace_t trace; + vec3_t point; + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + // we just transitioned into freefall + if ( pm->debugLevel ) + { + Com_Printf("%i:lift\n", c_pmove); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) + { + if ( pm->cmd.forwardmove >= 0 ) + { + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) +{ + vec3_t point; + trace_t trace; + float minWalkNormal; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // When stuck to antoher player set a flag to let the trigger code know so it can unstick the player + if ( (trace.allsolid || trace.startsolid) && trace.entityNum < MAX_CLIENTS ) + { + pm->ps->pm_flags |= PMF_SIAMESETWINS; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( pm->ps, LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( pm->ps, LEGS_JUMP_BACK ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.contents & CONTENTS_TERRAIN ) + { + minWalkNormal = MIN_WALK_NORMAL_TERRAIN; + } + else + { + minWalkNormal = MIN_WALK_NORMAL; + } + + if ( trace.plane.normal[2] < minWalkNormal ) + { + if ( pm->debugLevel ) + { + Com_Printf("%i:steep\n", c_pmove); + } + + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE) + { + // just hit the ground +// if ((pml.groundTrace.contents & CONTENTS_TERRAIN) && pml.previous_velocity[2] > -200) +// { +// } +// else + { + if ( pm->debugLevel ) + { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(trace.surfaceFlags & MATERIAL_MASK, trace.plane.normal ); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) + { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) +{ + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + pm->mins[2]; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + // See if we are on a ladder too + if ( !(pm->ps->pm_flags&PMF_LADDER_JUMP) && (cont & CONTENTS_LADDER) ) + { + if ( pm->ps->ladder == -1 ) + { + pm->ps->ladder = BG_FindLadder ( pm->ps->origin ); + } + + pm->ps->pm_flags |= PMF_LADDER; + } + else + { + pm->ps->ladder = -1; + pm->ps->pm_flags &= ~PMF_LADDER; + } + + if ( cont & MASK_WATER ) + { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) + { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) + { + pm->waterlevel = 3; + } + } + } +} + +/* +============== +PM_CheckCrouchJump + +Handles crouch jumping +============== +*/ +static void PM_CheckCrouchJump ( void ) +{ + // Already crouch jumping so check to see if its over + if ( pm->ps->pm_flags & PMF_CROUCH_JUMP ) + { + // If they are on the ground the crouch jump is over + if ( pml.groundPlane ) + { + pm->ps->pm_flags &= ~PMF_CROUCH_JUMP; + } + } + else + { + // If not on the ground and still heading up then crouch jump is possible. + if ( !pml.groundPlane && (pm->ps->pm_flags & PMF_JUMPING) && pm->cmd.upmove < 0 ) + { + pm->ps->pm_flags |= PMF_CROUCH_JUMP; + } + } + + // Check again if still crouch jumping and if so alter the view height + // so the client doesnt look like they are ducking in mid air + if ( (pm->ps->pm_flags & PMF_CROUCH_JUMP) && (pm->ps->pm_flags & PMF_JUMPING) ) + { + // If still ducked look for windows + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + trace_t trace; + vec3_t maxs; + + VectorCopy ( pm->maxs, maxs ); + maxs[2] = DEFAULT_PLAYER_Z_MAX; + + pm->trace (&trace, pm->ps->origin, pm->mins, maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if ( !(trace.allsolid || trace.startsolid) ) + { + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } + } + } +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( (pm == 0) || (pm->ps == 0) ) + { + return; + } + + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + + pm->mins[2] = MINS_Z; + + if (pm->ps->pm_type == PM_DEAD) + { + pm->maxs[2] = DEAD_PLAYER_Z_MAX; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + // duck or prone + if (pm->cmd.upmove < 0) + { + // assume ducked at first + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + pm->maxs[2] = DEFAULT_PLAYER_Z_MAX; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + { + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + } + + if ( (pm->ps->pm_flags & PMF_DUCKED) ) + { + pm->maxs[2] = CROUCH_PLAYER_Z_MAX; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + pm->maxs[2] = DEFAULT_PLAYER_Z_MAX; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) +{ + float bobmove; + int old; + qboolean footstep; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + + if ( pm->ps->pm_flags & PMF_LADDER ) + { + } + else + { + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_SWIM ); + } + return; + } + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) + { + if ( pm->xyspeed < 5 ) + { + pm->ps->bobCycle = 0; // start at beginning of cycle again + + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if ( pm->ps->leanTime - LEAN_TIME < 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_CROUCH_LEFT ); + } + else if ( pm->ps->leanTime - LEAN_TIME > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_CROUCH_RIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_IDLE_CROUCH ); + } + } + else + { + if ( pm->ps->leanTime - LEAN_TIME < 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_LEFT ); + } + else if ( pm->ps->leanTime - LEAN_TIME > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEAN_RIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, TORSO_IDLE_PISTOL ); + } + } + } + return; + } + + + footstep = qfalse; + + if ( (pm->ps->pm_flags & PMF_DUCKED) && (pm->ps->groundEntityNum != ENTITYNUM_NONE ) ) + { + bobmove = 0.25; // ducked characters bob much faster + + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_WALK_CROUCH_BACK ); + } + else + { + if ( pm->ps->leanTime - LEAN_TIME < 0 ) + { + if ( pm->cmd.rightmove > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_CROUCH_WALKRIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_CROUCH_WALKLEFT ); + } + } + else if ( pm->ps->leanTime - LEAN_TIME > 0 ) + { + if ( pm->cmd.rightmove > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_CROUCH_WALKRIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_CROUCH_WALKLEFT ); + } + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_WALK_CROUCH ); + } + } + } + else + { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) + { + PM_BeginZoomOut ( ); + + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_RUN_BACK ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_RUN ); + } + footstep = qtrue; + } + else + { + bobmove = 0.3f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_WALK_BACK ); + } + else + { + if ( pm->ps->leanTime - LEAN_TIME < 0 ) + { + if ( pm->cmd.rightmove > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_WALKRIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANLEFT_WALKLEFT ); + } + } + else if ( pm->ps->leanTime - LEAN_TIME > 0 ) + { + if ( pm->cmd.rightmove > 0 ) + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_WALKRIGHT ); + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_LEANRIGHT_WALKLEFT ); + } + } + else + { + PM_ContinueLegsAnim( pm->ps, LEGS_WALK ); + } + } + } + } + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + if ( pm->waterlevel == 0 ) + { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) + { + PM_FootstepForSurface(); + } + } + else if ( pm->waterlevel == 1 ) + { + // splashing + PM_AddEvent( EV_WATER_FOOTSTEP ); + } + else if ( pm->waterlevel == 2 ) + { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } + else if ( pm->waterlevel == 3 ) + { + // no sound when completely underwater + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) +{ + if ( !pml.previous_waterlevel && pm->waterlevel == 1 ) + { + PM_AddEvent( EV_WATER_TOUCH ); + } + else if ( pml.previous_waterlevel <= 1 && pm->waterlevel > 1 ) + { + if ( pm->ps->velocity[2] < -100 ) + { + PM_AddEvent( EV_WATER_LAND ); + } + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + +/* +=============== +PM_SetWeaponTime +=============== +*/ +static void PM_SetWeaponTime ( TAnimWeapon *aW ) +{ + TAnimInfoWeapon *aIW; + + if(!aW) + { + assert(0); + } + + // Weapon model info tells us how long the anim is + aIW = aW->mWeaponModelInfo; + if ( !aIW ) + { + return; + } + + pm->ps->weaponTime = 1000.0f / aIW->mFPS[0] * aIW->mNumFrames[0] / aIW->mSpeed; + pm->ps->weaponAnimTime = pm->ps->weaponTime; +} + +/* +=============== +BG_GetWeaponNote +=============== +*/ +TNoteTrack *BG_GetWeaponNote( playerState_t* ps, int weapon, int anim, int animChoice, int callbackStep ) +{ + TAnimWeapon *aW; + TAnimInfoWeapon *aIW; + TNoteTrack *note; + int n=0; + + note = NULL; + aW=BG_GetInviewAnimFromIndex( weapon, anim&~ANIM_TOGGLEBIT); + if (!aW) + { + return 0; + } + + aIW = aW->mWeaponModelInfo; + if ( !aIW ) + { + return 0; + } + + // Find the callback for the given step + for ( note = aIW->mNoteTracks[ps->weaponAnimIdChoice], n=0; note && n < callbackStep; note = note->mNext, n++ ) + { + // Do nothing, loop does it all + } + + return(note); +} + +/* +============== +PM_CheckWeaponNotes +============== +*/ +void PM_CheckWeaponNotes ( void ) +{ + playerState_t *ps; + TAnimWeapon *aW; + TAnimInfoWeapon *aIW; + TNoteTrack *note; + int step; + int stepTime; + + pm->ps->weaponCallbackTime += pml.msec; + + // 1st note step. + step = 0; + ps = pm->ps; + aW = BG_GetInviewAnimFromIndex ( ps->weapon, (ps->weaponAnimId&~ANIM_TOGGLEBIT) ); + + assert ( aW ); + if ( !aW ) + { + return; + } + + // Get the cached weapon model info + aIW = aW->mWeaponModelInfo; + if ( !aIW ) + { + return; + } + + stepTime = 1000.0f / aIW->mFPS[pm->ps->weaponAnimIdChoice] / aIW->mSpeed;; + note = aIW->mNoteTracks[pm->ps->weaponAnimIdChoice]; + + while(note) + { + if( pm->ps->weaponCallbackTime >= note->mFrame * stepTime ) + { + if(step > pm->ps->weaponCallbackStep) + { + if( !Q_stricmp("fire",note->mNote) || !Q_stricmp("altfire",note->mNote) ) + { + if(pm->ps->weaponstate==WEAPON_FIRING) + { + int seed; + + // Update the seed + seed = pm->ps->stats[STAT_SEED]; + Q_rand ( &seed ); + seed = seed & 0xFFFF; + pm->ps->stats[STAT_SEED] = seed; + + PM_AddEvent(EV_FIRE_WEAPON); + PM_Weapon_AddInaccuracy(ATTACK_NORMAL); + PM_Weapon_AddKickAngles(weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].maxKickAngles); + + + } + else if(pm->ps->weaponstate==WEAPON_FIRING_ALT) + { + PM_AddEvent(EV_ALT_FIRE); + PM_Weapon_AddKickAngles(weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].maxKickAngles); + } + } + + PM_AddEventWithParm ( EV_WEAPON_CALLBACK, + ((step&0xFF) << 24) + + ((pm->ps->weaponAnimIdChoice&0xFF)<<16) + + (((ps->weaponAnimId&~ANIM_TOGGLEBIT)&0xFF)<<8) + + pm->ps->weapon); + + pm->ps->weaponCallbackStep=step; + } + } + + step++; + note=note->mNext; + } +} + +/* +=============== +PM_SetWeaponAnimChoice +=============== +*/ +void PM_SetWeaponAnimChoice(TAnimWeapon *aW) +{ + TAnimInfoWeapon *aIW; + + if(!aW) + { + assert(0); + return; + } + + // Get the cached weapon model info + aIW = aW->mWeaponModelInfo; + if ( !aIW ) + { + return; + } + + pm->ps->weaponAnimIdChoice = rand()%aIW->mNumChoices; +} + +/* +=============== +PM_GetAnimFromName +=============== +*/ +TAnimWeapon* PM_GetAnimFromName ( char *animName, playerState_t *ps, int *animIndex ) +{ + TAnimWeapon *aW=0; + TAnimInfoWeapon *aIW=0; + char tempname[MAX_QPATH]; + + switch(ps->weapon) + { + case WP_KNIFE: + if(!strcmp(animName,"charge")) + { + // Get 'prefire' anim. + aW=BG_GetInviewAnim(pm->ps->weapon,"prefire",animIndex); + PM_SetWeaponAnimChoice(aW); + } + else if(!strcmp(animName,"fire")) + { + aW=BG_GetInviewAnimFromIndex(ps->weapon,ps->weaponAnimId&~ANIM_TOGGLEBIT); + if((!strcmp(aW->mName,"prefire"))||strstr(aW->mName,"firetrans")) + { + // Get 'fire' anim. + aW=BG_GetInviewAnim(ps->weapon,"fire",animIndex); + PM_SetWeaponAnimChoice(aW); + } + else if(!strcmp(aW->mName,"fire")) + { + // Get 'firetrans' anim. We don't call PM_SetWeaponAnimChoice() + // because the firetrans anims are matched to the fire anims. + aIW=BG_GetInviewModelAnim(ps->weapon,"weaponmodel","fire"); + strcpy(tempname,aIW->mTransition[ps->weaponAnimIdChoice]); + aW=BG_GetInviewAnim(ps->weapon,tempname,animIndex); + } + else + { + // Get 'prefire' anim. + aW=BG_GetInviewAnim(pm->ps->weapon,"fire",animIndex); + PM_SetWeaponAnimChoice(aW); + } + } + else if(!strcmp(animName,"fireend")) + { + aW=BG_GetInviewAnim(ps->weapon,"fireend1",animIndex); + PM_SetWeaponAnimChoice(aW); + } + else + { + // Nothing clever about the other sequences. + aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex); + PM_SetWeaponAnimChoice(aW); + } + break; + + case WP_MM1_GRENADE_LAUNCHER: + case WP_M590_SHOTGUN: + if(!strcmp(animName,"reload")) + { + aW=BG_GetInviewAnimFromIndex(ps->weapon,ps->weaponAnimId&~ANIM_TOGGLEBIT); + if(!strcmp(aW->mName,"reloadbegin")||!strcmp(aW->mName,"reloadshell")) + { + // Get 'reloadshell' anim. + aW=BG_GetInviewAnim(ps->weapon,"reloadshell",animIndex); + } + else + { + // Get 'reloadbegin' anim. + aW=BG_GetInviewAnim(pm->ps->weapon,"reloadbegin",animIndex); + } + } + else if(!strcmp(animName,"reloadend")) + { + // Get 'reloadend' anim. + aW=BG_GetInviewAnim(ps->weapon,"reloadend",animIndex); + } + else + { + // Nothing clever about the other sequences. + aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex); + } + PM_SetWeaponAnimChoice(aW); + break; + + case WP_M67_GRENADE: + case WP_M84_GRENADE: + case WP_F1_GRENADE: + case WP_L2A2_GRENADE: + case WP_MDN11_GRENADE: + case WP_SMOHG92_GRENADE: + case WP_ANM14_GRENADE: + case WP_M15_GRENADE: + if(!strcmp(animName,"charge")) + { + // Get 'throwbegin' anim. + aW=BG_GetInviewAnim(ps->weapon,"throwbegin",animIndex); + } + else if(!strcmp(animName,"altcharge")) + { + // Get 'throwbegin' anim. + aW=BG_GetInviewAnim(ps->weapon,"altthrowbegin",animIndex); + } + else if(!strcmp(animName,"fire")) + { + // Get 'throwend' anim. + aW=BG_GetInviewAnim(ps->weapon,"throwend",animIndex); + } + else if(!strcmp(animName,"altfire")) + { + // Get 'throwend' anim. + aW=BG_GetInviewAnim(ps->weapon,"altthrowend",animIndex); + } + else + { + // Nothing clever about the other sequences. + aW=BG_GetInviewAnim(pm->ps->weapon,animName,animIndex); + } + PM_SetWeaponAnimChoice(aW); + break; + + default: + // Other weapons don't do anything fancy. + aW=BG_GetInviewAnim(ps->weapon,animName,animIndex); + PM_SetWeaponAnimChoice(aW); + break; + } + + return(aW); +} + +/* +=============== +BG_GetWeaponAnim +=============== +*/ +TAnimWeapon *BG_GetWeaponAnim(int weaponAction,playerState_t *ps,int *animIndex) +{ + TAnimWeapon *aW=0; + + switch(weaponAction) + { + case WACT_READY: + aW=PM_GetAnimFromName("ready",ps,animIndex); + break; + case WACT_IDLE: + aW=PM_GetAnimFromName("idle",ps,animIndex); + break; + case WACT_FIRE: + aW=PM_GetAnimFromName("fire",ps,animIndex); + break; + case WACT_FIRE_END: + aW=PM_GetAnimFromName("fireend",ps,animIndex); + break; + case WACT_ALTFIRE: + aW=PM_GetAnimFromName("altfire",ps,animIndex); + break; + case WACT_ALTFIRE_END: + aW=PM_GetAnimFromName("altfireend",ps,animIndex); + break; + case WACT_RELOAD: + aW=PM_GetAnimFromName("reload",ps,animIndex); + break; + case WACT_ALTRELOAD: + aW=PM_GetAnimFromName("altreload",ps,animIndex); + break; + case WACT_RELOAD_END: + aW=PM_GetAnimFromName("reloadend",ps,animIndex); + break; + case WACT_PUTAWAY: + aW=PM_GetAnimFromName("done",ps,animIndex); + break; + case WACT_ZOOMIN: + aW=PM_GetAnimFromName("zoomin",ps,animIndex); + break; + case WACT_ZOOMOUT: + aW=PM_GetAnimFromName("zoomout",ps,animIndex); + break; + case WACT_CHARGE: + aW=PM_GetAnimFromName("charge",ps,animIndex); + break; + case WACT_ALTCHARGE: + aW=PM_GetAnimFromName("altcharge",ps,animIndex); + break; + default: + Com_Printf("Anim unknown: %i\n",weaponAction); + break; + } + + return(aW); +} + +/* +=============== +PM_HandleWeaponAction +=============== +*/ +static void PM_HandleWeaponAction(int weaponAction) +{ + TAnimWeapon *aW; + int animIndex; + + aW = BG_GetWeaponAnim ( weaponAction, pm->ps, &animIndex ); + if(!aW) + { + return; + } + + // Reset callback timer. We have to account for any remaining weapontime + // or we could miss off callbacks during sequences that are short in duration. + pm->ps->weaponCallbackTime = -pm->ps->weaponTime; + pm->ps->weaponCallbackStep = -1; + + PM_SetWeaponTime(aW); + if((pm->ps->weaponAnimId&~ANIM_TOGGLEBIT)==animIndex) + { + animIndex=pm->ps->weaponAnimId^ANIM_TOGGLEBIT; + } + + pm->ps->weaponAnimId = animIndex; +} + +/* +=============== +PM_BeginZoomIn +=============== +*/ +static void PM_BeginZoomIn(void) +{ + // Reset the zom fov if not rezooming + if ( !(pm->ps->pm_flags & PMF_ZOOM_REZOOM) ) + { + pm->ps->zoomFov = 0; + } + + pm->ps->weaponstate=WEAPON_ZOOMIN; + PM_HandleWeaponAction(WACT_ZOOMIN); +} + +/* +=============== +PM_BeginZoomOut +=============== +*/ +static void PM_BeginZoomOut(void) +{ + if ( !(pm->ps->pm_flags & PMF_ZOOMED) ) + { + return; + } + + if ( !(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD ) ) + { + pm->ps->zoomFov = 0; + } + + pm->ps->weaponstate=WEAPON_ZOOMOUT; + PM_HandleWeaponAction(WACT_ZOOMOUT); + pm->ps->zoomTime=pm->ps->commandTime; + pm->ps->pm_flags &= ~(PMF_ZOOM_LOCKED|PMF_ZOOM_REZOOM|PMF_ZOOMED); +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange(int weapon) +{ + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) + { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) + { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) + { + return; + } + + // Dont allow switching to the weapon the client is already using + if ( pm->ps->weapon == weapon ) + { + PM_AddEvent(EV_CHANGE_WEAPON_CANCELLED ); + + if ( pm->ps->weaponTime <= 0 ) + { + // Add a little delay so it doenst fire because this was caused by + // the menu selection + pm->ps->weaponTime = 150; + } + + return; + } + + // turn off any kind of zooming when weapon switching. + if( pm->ps->pm_flags & PMF_ZOOMED ) + { + pm->ps->zoomFov = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->pm_flags &= ~(PMF_ZOOM_FLAGS); + } + + // Clear the weapon time + pm->ps->weaponTime = 0; + pm->ps->weaponFireBurstCount = 0; + pm->ps->weaponAnimTime = 0; + + PM_AddEvent(EV_CHANGE_WEAPON); + pm->ps->weaponstate = WEAPON_DROPPING; + + if( pm->ps->weapon >= WP_M67_GRENADE && pm->ps->weapon <= WP_M15_GRENADE && pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] <= 0 ) + { + // We don't want to play the 'putaway' anim for the grenades if we are out of grenades! + return; + } + + PM_HandleWeaponAction(WACT_PUTAWAY); + + PM_StartTorsoAnim( pm->ps, weaponData[pm->ps->weapon].animDrop, pm->ps->weaponAnimTime ); +} + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) +{ + int weapon; + + weapon = pm->cmd.weapon & ~WP_DELAYED_CHANGE_BIT; + + if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) + { + weapon = WP_KNIFE; + } + + if(!( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) + { + weapon = WP_KNIFE; + } + + PM_AddEvent(EV_READY_WEAPON); + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime = 0; + pm->ps->weaponAnimTime = 0; + + // Default to auto (or next available fire mode). + if ( pm->ps->firemode[pm->ps->weapon] == WP_FIREMODE_NONE ) + { + pm->ps->firemode[pm->ps->weapon] = BG_FindFireMode ( pm->ps->weapon, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + } + + // We don't want to play the 'takeout' anim for the grenades if we are about to reload anyway + if( pm->ps->weapon >= WP_M67_GRENADE && pm->ps->weapon <= WP_M15_GRENADE && pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] <=0 ) + { + return; + } + + PM_HandleWeaponAction(WACT_READY); + + pm->ps->weaponTime = min(500,pm->ps->weaponTime); + + PM_StartTorsoAnim( pm->ps, weaponData[pm->ps->weapon].animRaise, pm->ps->weaponAnimTime ); +} + +/* +============== +PM_LoadShell + +Only used for the M590 shotgun. +============== +*/ +void PM_LoadShell(void) +{ + pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]++; + pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]--; +} + +/* +============== +PM_StartRefillClip +============== +*/ +void PM_StartRefillClip ( attackType_t attack ) +{ + int extra; + + assert ( attack >= ATTACK_NORMAL && attack < ATTACK_MAX ); + + // Sniper rifle should unzoom first before reloading. + if( pm->ps->pm_flags & PMF_ZOOMED ) + { + pm->ps->pm_flags |= PMF_ZOOM_DEFER_RELOAD; + PM_BeginZoomOut(); + return; + } + + pm->ps->weaponstate=(attack==ATTACK_ALTERNATE)?WEAPON_RELOADING_ALT:WEAPON_RELOADING; + pm->ps->weaponFireBurstCount=0; + + if(pm->ps->weapon!=WP_KNIFE) + { + // We don't want to play the reload anim for the knife, as it is part of + // the throw anim anyway. + if(attack==ATTACK_ALTERNATE) + { + PM_HandleWeaponAction(WACT_ALTRELOAD); + } + else + { + PM_HandleWeaponAction(WACT_RELOAD); + } + } + + if( pm->ps->weapon==WP_M590_SHOTGUN || pm->ps->weapon == WP_MM1_GRENADE_LAUNCHER ) + { + PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadStart, pm->ps->weaponTime ); + return; + } + + extra = weaponData[pm->ps->weapon].attack[attack].clipSize; + extra -= pm->ps->clip[attack][pm->ps->weapon]; + + if(pm->ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex]ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex]; + } + + pm->ps->clip[attack][pm->ps->weapon]+=extra; + pm->ps->ammo[weaponData[pm->ps->weapon].attack[attack].ammoIndex]-=extra; + + // Rezoom the sniper rifle, if it was zoomed before we started reloading. + if(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD ) + { + pm->ps->pm_flags |= PMF_ZOOM_REZOOM; + pm->ps->pm_flags &= ~PMF_ZOOM_DEFER_RELOAD; + } + + PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReload, pm->ps->weaponTime ); +} + +/* +============== +PM_EndRefillClip +============== +*/ +void PM_EndRefillClip(void) +{ + pm->ps->weaponstate=WEAPON_READY; +} + +/* +=============== +PM_GetAttackButtons +=============== +*/ +int PM_GetAttackButtons(void) +{ + int buttons=pm->cmd.buttons; + + // Debounce firemode select button. + if ( buttons & BUTTON_FIREMODE ) + { + if(!(pm->ps->pm_debounce & PMD_FIREMODE)) + { + pm->ps->pm_debounce |= PMD_FIREMODE; + } + else + { + buttons &= ~BUTTON_FIREMODE; + } + } + else + { + pm->ps->pm_debounce &= ~PMD_FIREMODE; + } + + // Handle firebutton in varous firemodes. + switch( pm->ps->firemode[pm->ps->weapon] ) + { + case WP_FIREMODE_AUTO: + break; + + case WP_FIREMODE_BURST: + // Debounce attack button and disable other buttons during burst fire. + if(buttons&BUTTON_ATTACK) + { + if( !(pm->ps->pm_debounce & PMD_ATTACK)) + { + pm->ps->pm_debounce |= PMD_ATTACK; + if(!pm->ps->weaponFireBurstCount) + { + pm->ps->weaponFireBurstCount=3; + } + } + else + { + buttons &= ~BUTTON_ATTACK; + } + } + else + { + pm->ps->pm_debounce &= ~PMD_ATTACK; + } + if(pm->ps->weaponFireBurstCount) + { + buttons|=BUTTON_ATTACK; + buttons&=~BUTTON_ALT_ATTACK; + buttons&=~BUTTON_RELOAD; + buttons&=~BUTTON_ZOOMIN; + buttons&=~BUTTON_ZOOMOUT; + buttons&=~BUTTON_FIREMODE; + } + break; + + case WP_FIREMODE_SINGLE: + // Debounce attack button. + if(buttons&BUTTON_ATTACK) + { + if(!(pm->ps->pm_debounce & PMD_ATTACK)) + { + pm->ps->pm_debounce |= PMD_ATTACK; + } + else + { + buttons&=~BUTTON_ATTACK; + } + } + else + { + pm->ps->pm_debounce &= ~PMD_ATTACK; + } + + break; + } + + // Handle single fire alt fire attacks or the sniper zoom + if ( pm->ps->weapon == WP_MSG90A1 || (weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].weaponFlags & (1<ps->pm_debounce & PMD_ALTATTACK ) ) + { + pm->ps->pm_debounce |= PMD_ALTATTACK; + } + else + { + buttons &= ~BUTTON_ALT_ATTACK; + } + } + else + { + pm->ps->pm_debounce &= ~PMD_ALTATTACK; + } + } + + return buttons; +} + +/* +============== +PM_Weapon_AddInaccuracy +============== +*/ +#define ACCURACY_FADERATE 0.2 +#define RECOVER_TIME 800 +#define RECOVER_TIME_SQ 200000.0 + +static void PM_Weapon_AddInaccuracy( attackType_t attack ) +{ + assert ( attack >= ATTACK_NORMAL && attack < ATTACK_MAX ); + + // Zoomed sniper weapons don't add innacuracy if ont hte ground + if( (pm->ps->pm_flags & PMF_ZOOMED) && pml.groundPlane ) + { + return; + } + + pm->ps->inaccuracy += weaponData[pm->ps->weapon].attack[attack].inaccuracy; + + pm->ps->inaccuracyTime = RECOVER_TIME; + + if ( pm->ps->inaccuracy > weaponData[pm->ps->weapon].attack[attack].maxInaccuracy ) + pm->ps->inaccuracy = weaponData[pm->ps->weapon].attack[attack].maxInaccuracy; +} + +/* +============== +PM_Weapon_UpdateInaccuracy +============== +*/ +static void PM_Weapon_UpdateInaccuracy(void) +{ + if( pm->ps->inaccuracy <= 0 ) + { + pm->ps->inaccuracy = 0; + return; + } + + if ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_FIRING_ALT) + { + pm->ps->inaccuracyTime -= (pml.msec * 3 / 4); + } + else + { + pm->ps->inaccuracyTime -= pml.msec; + } + + if ( pm->ps->inaccuracyTime <= 0 ) + { + pm->ps->inaccuracy = 0; + } + else + { + // decrement inaccuracy quadraticly to simulate the player recovering slowly at first, then rapidly + int diff = RECOVER_TIME - pm->ps->inaccuracyTime; + + pm->ps->inaccuracy *= (1 - (diff*diff)/RECOVER_TIME_SQ); + } +} + +/* +============== +PM_Weapon_AddKickAngles +============== +*/ +static void PM_Weapon_AddKickAngles(vec3_t kickAngles) +{ + // Throw the new kick angles into the integer versions + pm->ps->kickPitch += (int)(kickAngles[PITCH] * 500.0f); + + if ( pm->ps->kickPitch > 180000 ) + pm->ps->kickPitch = 180000; +} + +/* +============== +PM_Weapon_UpdateKickAngles +============== +*/ +static void PM_Weapon_UpdateKickAngles(void) +{ + // bring our kickAngles back down to zero over time + int i; + float degreesCorrectedPerMSecond = 0.01f; + qboolean firing; + float degreesToCorrect = degreesCorrectedPerMSecond*pml.msec; + + vec3_t kickAngles; + + // Extract the kick angles from their integer versions + kickAngles[YAW] = kickAngles[ROLL] = 0; + kickAngles[PITCH] = (float)pm->ps->kickPitch / 1000.0f; + + firing = qfalse; + + // Determine if firing or not + if ( pm->ps->weaponstate == WEAPON_FIRING || pm->ps->weaponstate == WEAPON_FIRING_ALT) + { + firing = qtrue; + } + + // If not firing then bring it down alot faster. + if (!firing) + { + // return a whole lot faster if not firing + VectorScale( kickAngles, 1.0f - (0.3f*((float)pml.msec/50.0f)), kickAngles ); + + for (i = 0; i < 3; i++) + { + if (kickAngles[i] >= 0 && kickAngles[i] < 0.05f) + { + kickAngles[i] = 0.0f; + } + else if (kickAngles[i] <= 0 && kickAngles[i] > -0.05f) + { + kickAngles[i] = 0.0f; + } + } + } + else + { + for (i = 0; i < 3; i++) + { + if (kickAngles[i] > 0) + { + if (kickAngles[i] < degreesToCorrect) + { + kickAngles[i] = 0; + } + else + { + kickAngles[i] -= degreesToCorrect; + } + } + else if (kickAngles[i] < 0) + { + if (kickAngles[i] > -degreesToCorrect) + { + kickAngles[i] = 0; + } + else + { + kickAngles[i] += degreesToCorrect; + } + } + } + } + + // Throw the new kick angles into the integer versions + pm->ps->kickPitch = (int)(kickAngles[PITCH] * 1000.0f); +} + +/* +============== +PM_Goggles + +Handles turning goggles on and off +============== +*/ +static void PM_Goggles ( void ) +{ + // ignore if not a normal player or dead or a ghost + if ( pm->ps->pm_type != PM_NORMAL || pm->ps->stats[STAT_HEALTH] <= 0 || (pm->ps->pm_flags & PMF_GHOST) ) + { + return; + } + + // See if they even have goggles + if ( pm->ps->stats[STAT_GOGGLES] == GOGGLES_NONE ) + { + return; + } + + // If the thermal goggles are on and the user has zoomed then turn them off + if ( pm->ps->stats[STAT_GOGGLES] == GOGGLES_INFRARED ) + { + // If the player is zoomed then no goggles + if ( pm->ps->pm_flags & PMF_ZOOMED ) + { + pm->ps->pm_flags &= ~PMF_GOGGLES_ON; + return; + } + } + + // When goggles button isnt down there is nothing to do + if ( !(pm->cmd.buttons & BUTTON_GOGGLES ) ) + { + pm->ps->pm_debounce &= ~PMD_GOGGLES; + return; + } + + // Dont do anything if the goggles button is being held down + if ( pm->ps->pm_debounce & PMD_GOGGLES ) + { + return; + } + + // toggle the goggles + pm->ps->pm_debounce |= PMD_GOGGLES; + pm->ps->pm_flags ^= PMF_GOGGLES_ON; + + // Play some noise + PM_AddEventWithParm(EV_GOGGLES, (pm->ps->pm_flags&PMF_GOGGLES_ON) ? qtrue : qfalse ); +} + +/* +============== +PM_WeaponIdle + +Handles the progression of the looping idle animation +============== +*/ +static void PM_WeaponIdle ( void ) +{ + pm->ps->weaponAnimTime -= pml.msec; + if ( pm->ps->weaponAnimTime <= 0 ) + { + pm->ps->weaponAnimTime = 0; + if ( pm->ps->weaponTime <= 0 ) + { + PM_HandleWeaponAction(WACT_IDLE); + pm->ps->weaponTime = 0; + } + } +} + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) +{ + int *ammoSource; + int attackButtons; + attackData_t *attackData; + qboolean altFire; + + // Get modifed attack buttons. + attackButtons = PM_GetAttackButtons(); + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) + { + return; + } + + // ignore if not a normal player + if ( pm->ps->pm_type != PM_NORMAL ) + { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + { + pm->ps->weapon = WP_NONE; + return; + } + + // Update the weapon inaccuracies and recoil + PM_Weapon_UpdateInaccuracy(); + PM_Weapon_UpdateKickAngles(); + + if( pm->ps->weaponTime > 0 ) + { + pm->ps->weaponTime-=pml.msec; + } + + // See if we've hit a note? + PM_CheckWeaponNotes(); + + // Check for weapon change. + if( pm->ps->weaponstate == WEAPON_CHARGING || pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + // Can't change if weapon is charging. + // Update the grenade timer + if ( pm->ps->grenadeTimer > 0 ) + { + pm->ps->grenadeTimer -= pml.msec; + + // Force it to go off if the timer has run out + if ( pm->ps->grenadeTimer <= 0 ) + { + pm->ps->grenadeTimer = 1; + pm->ps->weaponTime = 0; + attackButtons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } + } + } + else if ( pm->ps->weaponstate == WEAPON_SPAWNING ) + { + if ( pm->cmd.weapon != pm->ps->weapon ) + { + return; + } + + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 0; + } + else if( pm->ps->weaponTime <= 0 || pm->ps->weaponstate < WEAPON_RELOADING ) + { + // Dont change weapons if this is a weapon selection + if ( (pm->cmd.weapon & WP_DELAYED_CHANGE_BIT) && ((pm->cmd.buttons&BUTTON_ATTACK)||(pm->cmd.buttons&BUTTON_ALT_ATTACK)) ) + { + PM_BeginWeaponChange( pm->cmd.weapon & ~WP_DELAYED_CHANGE_BIT ); + } + else if ( !(pm->cmd.weapon & WP_DELAYED_CHANGE_BIT) && pm->ps->weapon != pm->cmd.weapon ) + { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) + { + // Handle the weapons idle animation + PM_WeaponIdle ( ); + + return; + } + + // Reload the alt clip immediately + if( !pm->ps->clip[ATTACK_ALTERNATE][pm->ps->weapon] && pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].ammoIndex] > 0) + { + switch(weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].fireFromClip) + { + case 2: + // Reload altclip. + PM_StartRefillClip( ATTACK_ALTERNATE ); + return; + } + } + + // Select firemode. + if( attackButtons & BUTTON_FIREMODE ) + { + pm->ps->firemode[pm->ps->weapon] = BG_FindFireMode( pm->ps->weapon, ATTACK_NORMAL, pm->ps->firemode[pm->ps->weapon] + 1 ); + } + + // Decrement burst fire counter if running. + if(pm->ps->weaponFireBurstCount) + { + pm->ps->weaponFireBurstCount--; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) + { + PM_FinishWeaponChange(); + return; + } + + // Zoom in animation complete... now set zoom parms. + if( pm->ps->weaponstate == WEAPON_ZOOMIN ) + { + // The zoomfov may still be remembered from a reload while zooming + if ( !pm->ps->zoomFov ) + { + pm->ps->zoomFov = 20; + } + + pm->ps->pm_flags |= PMF_ZOOMED; + pm->ps->pm_flags |= PMF_ZOOM_LOCKED; + pm->ps->pm_flags &= ~PMF_ZOOM_REZOOM; + pm->ps->weaponstate=WEAPON_READY; + return; + } + + if( pm->ps->weaponstate==WEAPON_CHARGING || pm->ps->weaponstate==WEAPON_CHARGING_ALT ) + { + switch(pm->ps->weapon) + { + case WP_M67_GRENADE: + case WP_M84_GRENADE: + case WP_F1_GRENADE: + case WP_L2A2_GRENADE: + case WP_MDN11_GRENADE: + case WP_SMOHG92_GRENADE: + case WP_ANM14_GRENADE: + case WP_M15_GRENADE: + if(!(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + { + if(pm->ps->weaponstate==WEAPON_CHARGING) + { + if ( pm->ps->grenadeTimer <= 1 ) + { + PM_AddEvent(EV_FIRE_WEAPON); + pm->ps->weaponstate=WEAPON_FIRING; + pm->ps->weaponTime = 250; + } + else + { + pm->ps->weaponstate=WEAPON_FIRING; + PM_HandleWeaponAction(WACT_FIRE); + PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_END, pm->ps->weaponTime); + } + } + else + { + if ( pm->ps->grenadeTimer <= 1 ) + { + PM_AddEvent(EV_ALT_FIRE); + pm->ps->weaponstate=WEAPON_FIRING; + pm->ps->weaponTime = 250; + } + else + { + pm->ps->weaponstate=WEAPON_FIRING_ALT; + PM_HandleWeaponAction(WACT_ALTFIRE); + PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_END, pm->ps->weaponTime); + } + } + return; + } + else + { + return; + } + break; + + default: + break; + } + } + + // Special end of fire animation for some weapons. Knife currently the only one. + if( pm->ps->weaponstate == WEAPON_FIRING ) + { + switch(pm->ps->weapon) + { + case WP_KNIFE: + if(!(pm->cmd.buttons&BUTTON_ATTACK)) + { + PM_HandleWeaponAction(WACT_FIRE_END); + pm->ps->weaponTime=0; + pm->ps->weaponstate=WEAPON_READY; + return; + } + default: + break; + } + } + + // ************************************************************************ + // Special reload behavior for warious weapons. + // ************************************************************************ + if((pm->ps->weaponstate==WEAPON_RELOADING)||(pm->ps->weaponstate==WEAPON_RELOADING_ALT)) + { + switch(pm->ps->weapon) + { + case WP_MM1_GRENADE_LAUNCHER: + case WP_M590_SHOTGUN: + // The M590 shotgun has a unique behavior in that when it is reloading, + // the reload can be interrupted by firing. This cancels the reload of + // course. + if(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)&&(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]>0)) + { + // Allow normal fire operation and cancel reload. Note: in + // the interests of gameplay, I allow us to go right to the + // fire anim, although it doesn't look as smooth as going to + // reload end and then fire. + PM_HandleWeaponAction(WACT_RELOAD_END); + PM_EndRefillClip(); + + PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadEnd, pm->ps->weaponTime ); + + return; + } + else if( ( pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] < weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].clipSize) && + ( pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]>0)) + { + // Load 1 more shell. + pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]++; + pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex]--; + PM_HandleWeaponAction(WACT_RELOAD); + + PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReload, pm->ps->weaponTime ); + return; + } + else + { + // Weapon fully loaded so play end reload sequence. + PM_HandleWeaponAction(WACT_RELOAD_END); + PM_EndRefillClip(); + + PM_StartTorsoAnim ( pm->ps, weaponData[pm->ps->weapon].animReloadEnd, pm->ps->weaponTime ); + + return; + } + break; + + default: + PM_EndRefillClip(); + break; + } + } + else if(pm->ps->pm_flags & PMF_ZOOM_DEFER_RELOAD ) + { + PM_StartRefillClip( ATTACK_NORMAL ); + return; + } + else if( pm->ps->weapon==WP_KNIFE || (pm->ps->weapon>=WP_RPG7_LAUNCHER && pm->ps->weapon<=WP_M15_GRENADE) ) + { + if(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]<1) + { + // Clip is now empty so see if we have enough ammo to reload this weapon? + if (pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex] > 0) + { + // Yes, so reload it. + PM_StartRefillClip( ATTACK_NORMAL ); + return; + } + else + { + if(pm->ps->weapon!=WP_RPG7_LAUNCHER) + { + // Clear grenade type from inventory. + pm->ps->stats[STAT_WEAPONS]&=~(1<ps->weapon); + + // Out of ammo so switch weapons. + PM_AddEventWithParm(EV_NOAMMO, pm->ps->weapon); + return; + } + } + } + } + + // Handle zooming in/out for sniper rifle. + if(pm->ps->weapon==WP_MSG90A1) + { + if( (attackButtons&BUTTON_ALT_ATTACK) || (pm->ps->pm_flags & PMF_ZOOM_REZOOM) ) + { + if( pm->ps->pm_flags & PMF_ZOOMED ) + { + PM_BeginZoomOut(); + } + else + { + PM_BeginZoomIn(); + } + return; + } + else if( pm->ps->pm_flags & PMF_ZOOMED ) + { + if(pm->cmd.buttons&BUTTON_ZOOMIN) + { + pm->ps->zoomFov = pm->ps->zoomFov >> 1; + if ( pm->ps->zoomFov < 5) + { + pm->ps->zoomFov = 5; + } + pm->ps->weaponTime=175; + return; + } + else if(pm->cmd.buttons&BUTTON_ZOOMOUT) + { + pm->ps->zoomFov = pm->ps->zoomFov << 1; + if(pm->ps->zoomFov > 20 ) + { + pm->ps->zoomFov = 20; + } + pm->ps->weaponTime=175; + return; + } + } + } + + // Reload weapon? + if ( attackButtons & BUTTON_RELOAD ) + { + if(pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon] < weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].clipSize) + { + // No, so see if we have enough ammo to reload this weapon? + if (pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].ammoIndex] > 0) + { + // Yes, so reload it. + PM_StartRefillClip( ATTACK_NORMAL ); + return; + } + } + } + + // Start weapon when either frozen or not shooting + if( pm->ps->stats[STAT_FROZEN] || !(attackButtons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + { + // Handle the weapons idle animation + PM_WeaponIdle ( ); + + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // Determine whether to use the alternate or normal attack info + if ( attackButtons & BUTTON_ATTACK ) + { + altFire = qfalse; + attackData = &weaponData[pm->ps->weapon].attack[ATTACK_NORMAL]; + } + else + { + altFire = qtrue; + attackData = &weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE]; + + // Cant throw last knife + if( pm->ps->weapon==WP_KNIFE && pm->ps->ammo[attackData->ammoIndex] < 1 ) + { + return; + } + } + + // Ammo taken from pool, clip or altclip? + switch(attackData->fireFromClip) + { + case 0: + ammoSource = &pm->ps->ammo[attackData->ammoIndex]; + break; + + default: + case 1: + ammoSource = &pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon]; + break; + + case 2: + ammoSource = &pm->ps->clip[ATTACK_ALTERNATE][pm->ps->weapon]; + break; + } + + // Is there enough ammo to fire? + if ( (*ammoSource) - attackData->fireAmount < 0 ) + { + // No, so reload if there is more ammo + if( pm->ps->ammo[ attackData->ammoIndex ] > 0 ) + { + // If auto reloading is enabled then reload the gun + if ( pm->ps->pm_flags & PMF_AUTORELOAD ) + { + switch ( attackData->fireFromClip) + { + case 1: + // Reload clip. + PM_StartRefillClip( ATTACK_NORMAL ); + return; + + case 2: + // Reload altclip. + PM_StartRefillClip( ATTACK_ALTERNATE ); + return; + } + } + } + // Out of ammo, switch weapons if not an alt-attack + else if ( !altFire ) + { + PM_AddEventWithParm (EV_NOAMMO, pm->ps->weapon); + } + + // Handle the weapons idle animation + PM_WeaponIdle ( ); + + pm->ps->weaponstate = WEAPON_READY; + + return; + } + + // This attack doesnt exist + if ( !attackData->damage ) + { + // Handle the weapons idle animation + PM_WeaponIdle ( ); + + pm->ps->weaponstate = WEAPON_READY; + + return; + } + + // Decrease the ammo + (*ammoSource) -= attackData->fireAmount; + + // Handle charging cases + switch(pm->ps->weapon) + { + case WP_KNIFE: + + if ( altFire ) + { + if( pm->ps->weaponstate != WEAPON_FIRING && pm->ps->weaponstate != WEAPON_FIRING_ALT ) + { + PM_HandleWeaponAction(WACT_ALTFIRE); + pm->ps->weaponstate=WEAPON_FIRING_ALT; + } + } + else + { + PM_HandleWeaponAction ( WACT_FIRE ); + pm->ps->weaponstate = WEAPON_FIRING; + } + + // Play the torso animation associated with the attack + PM_StartTorsoAnim ( pm->ps, attackData->animFire, pm->ps->weaponAnimTime ); + + break; + + case WP_M67_GRENADE: + case WP_M84_GRENADE: + case WP_F1_GRENADE: + case WP_L2A2_GRENADE: + case WP_MDN11_GRENADE: + case WP_SMOHG92_GRENADE: + case WP_ANM14_GRENADE: + case WP_M15_GRENADE: + + // Start the detonation timer on the grenade going. + pm->ps->grenadeTimer = attackData->projectileLifetime; + + if ( altFire ) + { + PM_HandleWeaponAction ( WACT_ALTCHARGE ); + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + } + else + { + PM_HandleWeaponAction ( WACT_CHARGE ); + pm->ps->weaponstate = WEAPON_CHARGING; + } + + PM_StartTorsoAnim( pm->ps, TORSO_ATTACK_GRENADE_START, pm->ps->weaponTime); + + break; + + default: + + if ( altFire ) + { + PM_HandleWeaponAction(WACT_ALTFIRE); + pm->ps->weaponstate=WEAPON_FIRING_ALT; + } + else + { + PM_HandleWeaponAction(WACT_FIRE); + pm->ps->weaponstate=WEAPON_FIRING; + } + + pm->ps->weaponTime += attackData->fireDelay; + + // Play the torso animation associated with the attack + if ( pm->ps->pm_flags & PMF_ZOOMED ) + { + PM_StartTorsoAnim ( pm->ps, attackData->animFireZoomed, pm->ps->weaponTime ); + } + else + { + PM_StartTorsoAnim ( pm->ps, attackData->animFire, pm->ps->weaponTime ); + } + + break; + } +} + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) + { + // drop misc timing counter + if ( pm->ps->pm_time ) + { + if ( pml.msec >= pm->ps->pm_time ) + { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + else + { + pm->ps->pm_time -= pml.msec; + } + } +} + +/* +============== +PM_CheckLean +============== +*/ +static void PM_CheckLean( void ) +{ + trace_t trace; + qboolean canlean; + float leanTime; + + if ( !pm || !pm->ps ) + { + return; + } + + // No leaning as a spectator or a ghost + if ( (pm->ps->pm_flags & PMF_GHOST) || pm->ps->pm_type == PM_SPECTATOR ) + { + pm->ps->leanTime = LEAN_TIME; + pm->ps->pm_flags &= ~PMF_LEANING; + return; + } + + leanTime = (float)pm->ps->leanTime - LEAN_TIME; + canlean = qfalse; + + // If their lean button is being pressed and they are on the ground then perform the lean + if( (pm->cmd.buttons & (BUTTON_LEAN_RIGHT|BUTTON_LEAN_LEFT)) && (pm->ps->groundEntityNum != ENTITYNUM_NONE) ) + { + vec3_t start, end, right, mins, maxs; + int leanDir; + + if( pm->cmd.buttons & BUTTON_LEAN_RIGHT ) + { + leanDir = 1; + } + else + { + leanDir = -1; + } + + // check for collision + VectorCopy( pm->ps->origin, start ); + start[2] += pm->ps->viewheight; + AngleVectors( pm->ps->viewangles, NULL, right, NULL ); + VectorSet( mins, -6, -6, -8 ); + VectorSet( maxs, 6, 6, 8 ); + + // since we're moving the camera over + // check that move + VectorMA( start, leanDir * LEAN_OFFSET * 1.25f, right, end ); + pm->trace(&trace, start, mins, maxs, end, pm->ps->clientNum, pm->tracemask ); + + if ( trace.fraction < 0 || trace.fraction >= 1.0f ) + { + leanTime += (leanDir * pml.msec); + if( leanTime > LEAN_TIME ) + { + leanTime = LEAN_TIME; + } + else if( leanTime < -LEAN_TIME ) + { + leanTime = -LEAN_TIME; + } + + canlean = qtrue; + } + else if ( (pm->ps->pm_flags&PMF_LEANING) && trace.fraction < 1.0f ) + { + int templeanTime = (float)leanDir * (float)LEAN_TIME * trace.fraction; + + if ( fabs(templeanTime) < fabs(leanTime) ) + { + leanTime = templeanTime; + } + } + } + + if ( !canlean ) + { + if( leanTime > 0 ) + { + leanTime -= pml.msec; + if( leanTime < 0 ) + { + leanTime = 0; + } + } + else if ( leanTime < 0 ) + { + leanTime += pml.msec; + if( leanTime > 0 ) + { + leanTime = 0; + } + } + } + + // Set a pm flag for leaning for convienience + if ( leanTime != 0 ) + { + pm->ps->pm_flags |= PMF_LEANING; + } + else + { + pm->ps->pm_flags &= ~PMF_LEANING; + } + + // The lean time is kept positive by adding in the base lean time + pm->ps->leanTime = (int) (leanTime + LEAN_TIME); +} + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) +{ + short temp; + int i; + vec3_t kickAngles; + + if ( ps->pm_type == PM_INTERMISSION) + { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) + { + return; // no view changes at all + } + + // Extract the kcik angles + kickAngles[PITCH] = ((float)ps->kickPitch / 1000.0f); + kickAngles[YAW] = kickAngles[ROLL] = 0; + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) + { + temp = cmd->angles[i] + ps->delta_angles[i] - ANGLE2SHORT(kickAngles[i]); + if ( i == PITCH ) + { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) + { + ps->delta_angles[i] = 16000 - (cmd->angles[i]- ANGLE2SHORT(kickAngles[i])); + temp = 16000; + } + else if ( temp < -16000 ) + { + ps->delta_angles[i] = -16000 - (cmd->angles[i] - ANGLE2SHORT(kickAngles[i])); + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + + PM_CheckLean ( ); +} + +/* +================ +PM_AdjustAttackStates +================ +*/ + +void PM_AdjustAttackStates( pmove_t *pm ) +{ + int ammoOk; + + // Check ammo after usage... + if(pm->cmd.buttons & BUTTON_ALT_ATTACK) + { + ammoOk = pm->ps->ammo[weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].ammoIndex] - weaponData[pm->ps->weapon].attack[ATTACK_ALTERNATE].fireAmount; + } + else + { + ammoOk = pm->ps->clip[ATTACK_NORMAL][pm->ps->weapon ] - weaponData[pm->ps->weapon].attack[ATTACK_NORMAL].fireAmount; + } + + // Set the firing flag. + if(!(pm->ps->pm_flags & PMF_RESPAWNED) && (pm->ps->pm_type!=PM_INTERMISSION) && (ammoOk>=0)) + { + if((pm->cmd.buttons & BUTTON_ALT_ATTACK)||(pm->ps->weaponstate==WEAPON_FIRING_ALT)) + { + pm->ps->eFlags |= EF_ALT_FIRING; + } + else if((pm->cmd.buttons & BUTTON_ATTACK)||(pm->ps->weaponstate==WEAPON_FIRING)) + { + pm->ps->eFlags &= ~EF_ALT_FIRING; + } + + // This flag should always get set, even when alt-firing + pm->ps->eFlags |= EF_FIRING; + } + else + { + // Clear 'em out + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + } +} + +/* +================ +PmoveSingle +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle (pmove_t *pmove) { + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // When the lean modifier button is held the strafe left and right keys + // will act as lean left and right + if ( pm->cmd.buttons & BUTTON_LEAN ) + { + pm->cmd.buttons &= ~(BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT); + + // Strafe left = lean left + if ( pm->cmd.rightmove < 0 ) + { + pm->cmd.buttons |= BUTTON_LEAN_LEFT; + } + // Strafe right = lean right + else if ( pm->cmd.rightmove > 0 ) + { + pm->cmd.buttons |= BUTTON_LEAN_RIGHT; + } + + // NO strafing with lean button down + pm->cmd.rightmove = 0; + } + + // Cant move when leaning + if ( (pm->cmd.buttons & (BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT))) + { +// pm->cmd.rightmove = 0; + pm->cmd.forwardmove = 0; + + // Cant jump when leaning + if ( pm->cmd.upmove > 0 ) + { + pm->cmd.upmove = 0; + } + } + + // Cant run when zoomed + if ( (pm->ps->pm_flags&PMF_ZOOMED) || pm->ps->weaponstate == WEAPON_ZOOMIN || (pm->cmd.buttons & (BUTTON_LEAN_LEFT|BUTTON_LEAN_RIGHT)) ) + { + if ( pm->cmd.forwardmove > 64 ) + { + pm->cmd.forwardmove = 64; + } + else if ( pm->cmd.forwardmove < -64 ) + { + pm->cmd.forwardmove = -64; + } + + + if ( pm->cmd.rightmove > 64 ) + { + pm->cmd.rightmove = 64; + } + else if ( pm->cmd.rightmove < -64 ) + { + pm->cmd.rightmove = -64; + } + + pm->cmd.buttons |= BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) + { + pm->ps->eFlags |= EF_TALK; + } + else + { + pm->ps->eFlags &= ~EF_TALK; + } + + // In certain situations, we may want to control which attack buttons are pressed and what kind of functionality + // is attached to them + PM_AdjustAttackStates( pm ); + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && !( pm->cmd.buttons & BUTTON_ATTACK) ) + { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) + { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + pm->ps->commandTime = pmove->cmd.serverTime; + + // Frozen? + if ( pm->ps->stats[STAT_FROZEN] ) + { + pm->ps->stats[STAT_FROZEN] -= pml.msec; + if ( pm->ps->stats[STAT_FROZEN] < 0 ) + { + pm->ps->stats[STAT_FROZEN] = 0; + } + else + { +// pm->cmd.buttons = pm->cmd.buttons & (BUTTON_RELOAD|BUTTON_ATTACK|BUTTON_); + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + } + + // save old org in case we get stuck + VectorCopy (pm->ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (pm->ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 ) + { + // not holding jump + pm->ps->pm_debounce &= ~PMD_JUMP; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) + { + pm->mins[0] = -15; + pm->mins[1] = -15; + pm->maxs[0] = 15; + pm->maxs[1] = 15; + pm->mins[2] = MINS_Z; + pm->maxs[2] = DEFAULT_PLAYER_Z_MAX; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + +// PM_FlyMove (); + PM_NoclipMove ( ); + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if (pm->ps->pm_type == PM_FREEZE) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION ) + { + return; // no movement at all + } + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + PM_CheckCrouchJump ( ); + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + PM_DropTimers(); + + if ( pm->ps->pm_flags & PMF_LADDER ) + { + PM_LadderMove ( ); + } + else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + PM_WaterJumpMove(); + } + else if ( pm->waterlevel > 1 ) + { + // swimming + PM_WaterMove(); + } + else if ( pml.walking ) + { + // walking on ground + PM_WalkMove(); + } + else + { + // airborne + PM_AirMove(); + } + + // set groundentity, watertype, and waterlevel + if(!(VectorCompare(pm->ps->origin,pml.previous_origin))) + { + // If we didn't move at all, then why bother doing this again -MW. + PM_GroundTrace(); + } + + PM_SetWaterLevel(); + + // turn goggles on/off + PM_Goggles ( ); + + // weapons + PM_Weapon(); + + // torso animation + PM_TorsoAnimation( pm->ps ); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + +/* +================ +PM_UpdatePVSOrigin + +The pvs of the client is calculated using its own origin and this function +ensures that the origin is set correctly. The main reason for having a PVS +origin is that when leaning your can poke around corners which will in turn +change what you can see. +================ +*/ +void PM_UpdatePVSOrigin ( pmove_t *pmove ) +{ + pm = pmove; + + // Set a pm flag for leaning and calculate the view origin for the lean + if ( pm->ps->leanTime - LEAN_TIME != 0 ) + { + vec3_t right; + float leanOffset; + + leanOffset = (float)(pm->ps->leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET; + + AngleVectors( pm->ps->viewangles, NULL, right, NULL ); + VectorMA( pm->ps->origin, leanOffset, right, pm->ps->pvsOrigin ); + } + else + { + VectorCopy ( pm->ps->origin, pm->ps->pvsOrigin ); + } +} + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } + else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + PM_UpdatePVSOrigin ( pmove ); + + if ( pmove->ps->pm_debounce & PMD_JUMP ) + { + pmove->cmd.upmove = 20; + } + } +} + +/* +================ +BG_AddLadder + +Adds a ladder to the ladder list +================ +*/ +void BG_AddLadder ( vec3_t absmin, vec3_t absmax, vec3_t fwd ) +{ + pm_ladders[pm_laddercount].origin[0] = (absmax[0] + absmin[0]) / 2; + pm_ladders[pm_laddercount].origin[1] = (absmax[1] + absmin[1]) / 2; + pm_ladders[pm_laddercount].origin[2] = (absmax[2] + absmin[2]) / 2; + VectorCopy ( fwd, pm_ladders[pm_laddercount].fwd ); + pm_laddercount++; +} + +/* +================ +BG_FindLadder + +Searches through the ladder list and finds the closes to the given origin +================ +*/ +int BG_FindLadder ( vec3_t pos ) +{ + int ladder; + int result; + float dist; + + dist = 999999.0f; + result = -1; + + for ( ladder = 0; ladder < pm_laddercount; ladder ++ ) + { + float dist2 = DistanceSquared( pos, pm_ladders[ladder].origin ); + + if ( dist2 < dist ) + { + dist = dist2; + result = ladder; + } + } + + return result; +} + diff --git a/code/game/bg_public.h b/code/game/bg_public.h new file mode 100644 index 0000000..f05868d --- /dev/null +++ b/code/game/bg_public.h @@ -0,0 +1,1137 @@ +// Copyright (C) 2001-2002 Raven Software. +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#ifndef __BG_PUBLIC_H__ +#define __BG_PUBLIC_H__ + +#include "bg_weapons.h" +#include "../../ui/menudef.h" +#include "inv.h" + +//#define GAME_VERSION "sof2mp-0.01" sent on 11/26/2001 +//#define GAME_VERSION "sof2mp-0.02" sent on 12/10/2001 +//#define GAME_VERSION "sof2mp-0.03" sent on 12/16/2001 +//#define GAME_VERSION "sof2mp-0.081" sent on 1/15/2002 +//#define GAME_VERSION "sof2mp-0.09" sent on 1/24/2002 +//#define GAME_VERSION "sof2mp-0.10" sent on 1/31/2002 +//#define GAME_VERSION "sof2mp-0.11" sent on 2/7/2002 +//#define GAME_VERSION "sof2mp-0.12" sent on 2/14/2002 +//#define GAME_VERSION "sof2mp-0.13" sent on 2/21/2002 +//#define GAME_VERSION "sof2mp-0.13b" public beta #1 on 3/1/3002 +//#define GAME_VERSION "sof2mp-0.14" sent on 3/4/2002 +//#define GAME_VERSION "sof2mp-0.15" sent on 3/11/2002 +//#define GAME_VERSION "sof2mp-0.15b" public beta #2 on 3/13/2002 +//#define GAME_VERSION "sof2mp-0.16" sent on 3/18/2002 +//#define GAME_VERSION "sof2mp-0.16b" public beta #3 on 3/20/2002 +//#define GAME_VERSION "sof2mp-0.17" sent on 3/24/2002 +//#define GAME_VERSION "sof2mp-1.01t" sent on 3/28/2002 +//#define GAME_VERSION "sof2mp-0.18" sent on 4/1/2002 - April Fools! +//#define GAME_VERSION "sof2mp-1.02t" sent on 4/5/2002 +//#define GAME_VERSION "sof2mp-0.19" sent on 4/8/2002 +//#define GAME_VERSION "sof2mp-0.20" sent on 4/15/2002 - Tax Day! +//#define GAME_VERSION "sof2mp-0.21" sent on 4/22/2002 +//#define GAME_VERSION "sof2mp-1.00.22" sent on 4/26/2002 +//#define GAME_VERSION "sof2mp-1.00.23" sent on 4/27/2002 +#ifdef GERMAN_BUILD + #define GAME_VERSION "sof2mp-1.00g" +#else + #define GAME_VERSION "sof2mp-1.00" +#endif + +#define DEFAULT_GRAVITY 800 +#define ARMOR_PROTECTION 0.55 + +#define MAX_ITEMS 256 +#define MAX_HEALTH 100 +#define MAX_ARMOR 100 + +#define BULLET_SPACING 95 + +#define DISMEMBER_HEALTH -20 + +#define RANK_TIED_FLAG 0x4000 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define DEFAULT_PLAYER_Z_MAX 43 +#define CROUCH_PLAYER_Z_MAX 18 +#define PRONE_PLAYER_Z_MAX -12 +#define DEAD_PLAYER_Z_MAX -30 + +#define DUCK_ACCURACY_MODIFIER 0.75f +#define JUMP_ACCURACY_MODIFIER 1.5f + +#define MINS_Z -46 + +#define DEFAULT_VIEWHEIGHT 37 +#define CROUCH_VIEWHEIGHT 8 +#define PRONE_VIEWHEIGHT -22 +#define DEAD_VIEWHEIGHT -38 + +#define BODY_SINK_DELAY 10000 +#define BODY_SINK_TIME 1500 + +#define LEAN_TIME 250 +#define LEAN_OFFSET 30 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h + +#define CS_PLAYERS 2 + +enum +{ +// CS_SERVERINFO, +// CS_SYSTEMINFO, +// CS_PLAYERS, + + CS_MUSIC = CS_CUSTOM, + + CS_MESSAGE, + CS_MOTD, + CS_WARMUP, + + CS_VOTE_TIME, + CS_VOTE_STRING, + CS_VOTE_YES, + CS_VOTE_NO, + CS_VOTE_NEEDED, + + CS_GAME_VERSION, + CS_GAME_ID, + CS_LEVEL_START_TIME, + CS_INTERMISSION, + CS_SHADERSTATE, + CS_BOTINFO, + + CS_GAMETYPE_TIMER, + CS_GAMETYPE_MESSAGE, + CS_GAMETYPE_REDTEAM, + CS_GAMETYPE_BLUETEAM, + + CS_ITEMS, + + CS_PICKUPSDISABLED, + + // Config string ranges + CS_MODELS, + CS_SOUNDS = CS_MODELS + MAX_MODELS, + CS_LOCATIONS = CS_SOUNDS + MAX_SOUNDS, + CS_LADDERS = CS_LOCATIONS + MAX_LOCATIONS, + CS_BSP_MODELS = CS_LADDERS + MAX_LADDERS, + CS_TERRAINS = CS_BSP_MODELS + MAX_SUB_BSP, + CS_EFFECTS = CS_TERRAINS + MAX_TERRAINS, + CS_LIGHT_STYLES = CS_EFFECTS + MAX_FX, + CS_ICONS = CS_LIGHT_STYLES + (MAX_LIGHT_STYLES*3), + CS_TEAM_INFO = CS_ICONS + MAX_ICONS, + CS_AMBIENT_SOUNDSETS = CS_TEAM_INFO + TEAM_NUM_TEAMS, + + CS_MAX = CS_AMBIENT_SOUNDSETS + MAX_AMBIENT_SOUNDSETS +}; + +/* +#define CS_MUSIC 68 +#define CS_MESSAGE 69 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 +#define CS_VOTE_NEEDED 12 + +#define CS_GAME_VERSION 16 +#define CS_LEVEL_START_TIME 17 // so the timer only shows the current level +#define CS_INTERMISSION 18 // when 1, scorelimit/timelimit has been hit and intermission will start in a second or two +#define CS_SHADERSTATE 19 +#define CS_BOTINFO 20 + +#define CS_GAMETYPE_TIMER 21 // currently visible timer +#define CS_GAMETYPE_MESSAGE 22 // Last gametype message +#define CS_GAMETYPE_REDTEAM 23 // red team group name +#define CS_GAMETYPE_BLUETEAM 24 // blue team group name + +#define CS_ITEMS 28 // string of 0's and 1's that tell which items are present + +// these are also in be_aas_def.h - argh (rjr) +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_CHARSKINS (CS_PLAYERS+MAX_CLIENTS) +#define CS_LOCATIONS (CS_CHARSKINS+MAX_CHARSKINS) +#define CS_LADDERS (CS_LOCATIONS + MAX_LOCATIONS) +#define CS_BSP_MODELS (CS_LADDERS + MAX_LADDERS) +#define CS_TERRAINS (CS_BSP_MODELS + MAX_SUB_BSP) +#define CS_EFFECTS (CS_PARTICLES+MAX_LOCATIONS) +#define CS_LIGHT_STYLES (CS_EFFECTS + MAX_FX) +#define CS_ICONS (CS_LIGHT_STYLES + (MAX_LIGHT_STYLES*3)) +#define CS_TEAM_INFO (CS_ICONS+MAX_ICONS) +#define CS_AMBIENT_SOUNDSETS (CS_TEAM_INFO+TEAM_NUM_TEAMS) + +#define CS_MAX (CS_AMBIENT_SOUNDSETS+MAX_AMBIENT_SOUNDSETS) +*/ + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +// animations +typedef enum +{ + BOTH_DEATH_NORMAL, + + ANIM_START_DEATHS, + + BOTH_DEATH_NECK, + BOTH_DEATH_CHEST_1, + BOTH_DEATH_CHEST_2, + BOTH_DEATH_GROIN_1, + BOTH_DEATH_GROIN_2, + BOTH_DEATH_GUT_1, + BOTH_DEATH_GUT_2, + BOTH_DEATH_HEAD_1, + BOTH_DEATH_HEAD_2, + BOTH_DEATH_SHOULDER_LEFT_1, + BOTH_DEATH_SHOULDER_LEFT_2, + BOTH_DEATH_ARMS_LEFT_1, + BOTH_DEATH_ARMS_LEFT_2, + BOTH_DEATH_LEGS_LEFT_1, + BOTH_DEATH_LEGS_LEFT_2, + BOTH_DEATH_LEGS_LEFT_3, + BOTH_DEATH_THIGH_LEFT_1, + BOTH_DEATH_THIGH_LEFT_2, + BOTH_DEATH_ARMS_RIGHT_1, + BOTH_DEATH_ARMS_RIGHT_2, + BOTH_DEATH_LEGS_RIGHT_1, + BOTH_DEATH_LEGS_RIGHT_2, + BOTH_DEATH_LEGS_RIGHT_3, + BOTH_DEATH_SHOULDER_RIGHT_1, + BOTH_DEATH_SHOULDER_RIGHT_2, + BOTH_DEATH_THIGH_RIGHT_1, + BOTH_DEATH_THIGH_RIGHT_2, + + ANIM_END_DEATHS, + + TORSO_DROP, + TORSO_DROP_ONEHANDED, + TORSO_DROP_KNIFE, + TORSO_RAISE, + TORSO_RAISE_ONEHANDED, + TORSO_RAISE_KNIFE, + + LEGS_IDLE, + LEGS_IDLE_CROUCH, + LEGS_WALK, + LEGS_WALK_BACK, + LEGS_WALK_CROUCH, + LEGS_WALK_CROUCH_BACK, + + LEGS_RUN, + LEGS_RUN_BACK, + + LEGS_SWIM, + + LEGS_JUMP, + LEGS_JUMP_BACK, + + LEGS_TURN, + + LEGS_LEAN_LEFT, + LEGS_LEAN_RIGHT, + LEGS_LEAN_CROUCH_LEFT, + LEGS_LEAN_CROUCH_RIGHT, + + LEGS_LEANLEFT_WALKLEFT, + LEGS_LEANLEFT_WALKRIGHT, + LEGS_LEANRIGHT_WALKLEFT, + LEGS_LEANRIGHT_WALKRIGHT, + + LEGS_LEANLEFT_CROUCH_WALKLEFT, + LEGS_LEANLEFT_CROUCH_WALKRIGHT, + LEGS_LEANRIGHT_CROUCH_WALKLEFT, + LEGS_LEANRIGHT_CROUCH_WALKRIGHT, + + TORSO_IDLE_KNIFE, + TORSO_IDLE_PISTOL, + TORSO_IDLE_RIFLE, + TORSO_IDLE_MSG90A1, + TORSO_IDLE_MSG90A1_ZOOMED, + TORSO_IDLE_M4, + TORSO_IDLE_M590, + TORSO_IDLE_USAS12, + TORSO_IDLE_RPG, + TORSO_IDLE_M60, + TORSO_IDLE_MM1, + TORSO_IDLE_GRENADE, + + TORSO_ATTACK_KNIFE, + TORSO_ATTACK_KNIFE_THROW, + TORSO_ATTACK_PISTOL, + TORSO_ATTACK_RIFLE, + TORSO_ATTACK_MSG90A1_ZOOMED, + TORSO_ATTACK_M4, + TORSO_ATTACK_M590, + TORSO_ATTACK_USAS12, + TORSO_ATTACK_RIFLEBUTT, + TORSO_ATTACK_RPG, + TORSO_ATTACK_M60, + TORSO_ATTACK_MM1, + TORSO_ATTACK_GRENADE_START, + TORSO_ATTACK_GRENADE_END, + TORSO_ATTACK_BAYONET, + TORSO_ATTACK_PISTOLWHIP, + + TORSO_RELOAD_M60, + TORSO_RELOAD_PISTOL, + TORSO_RELOAD_RIFLE, + TORSO_RELOAD_MSG90A1, + TORSO_RELOAD_RPG, + TORSO_RELOAD_USAS12, + + TORSO_RELOAD_M590_START, + TORSO_RELOAD_M590_SHELL, + TORSO_RELOAD_M590_END, + + TORSO_RELOAD_MM1_START, + TORSO_RELOAD_MM1_SHELL, + TORSO_RELOAD_MM1_END, + + MAX_ANIMATIONS + +} animNumber_t; + + + +typedef struct animation_s { + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + +typedef struct ladder_s +{ + vec3_t origin; + vec3_t fwd; + +} ladder_t; + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT 2048 // Note that there are 12 bits (max 4095) for animations. +#define ITEM_AUTOSWITCHBIT (1<<31) + +typedef enum +{ + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION, // no movement or status bar + +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_SPAWNING, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_RELOADING, + WEAPON_RELOADING_ALT, + WEAPON_FIRING, + WEAPON_FIRING_ALT, + WEAPON_CHARGING, + WEAPON_CHARGING_ALT, + WEAPON_ZOOMIN, + WEAPON_ZOOMOUT, +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED 0x00000001 +#define PMF_BACKWARDS_JUMP 0x00000002 // go into backwards land +#define PMF_JUMPING 0x00000004 // executing a jump +#define PMF_BACKWARDS_RUN 0x00000008 // coast down to backwards run +#define PMF_TIME_LAND 0x00000010 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 0x00000020 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 0x00000040 // pm_time is waterjump +#define PMF_RESPAWNED 0x00000080 // clear after attack and jump buttons come up +#define PMF_CAN_USE 0x00000100 // The server updated the animation, the pmove should set the ghoul2 anim to match. +#define PMF_FOLLOW 0x00000200 // spectate following another player +#define PMF_SCOREBOARD 0x00000400 // spectate as a scoreboard +#define PMF_GHOST 0x00000800 // Your a ghost. scarry!! +#define PMF_LADDER 0x00001000 // On a ladder +#define PMF_LADDER_JUMP 0x00002000 // Jumped off a ladder + +#define PMF_ZOOMED 0x00004000 +#define PMF_ZOOM_LOCKED 0x00008000 // Zoom mode cant be changed +#define PMF_ZOOM_REZOOM 0x00010000 // Rezoom after reload done +#define PMF_ZOOM_DEFER_RELOAD 0x00020000 // Reload after zoomout + +#define PMF_LIMITED_INVENTORY 0x00040000 // inventory is limited for this player + +#define PMF_CROUCH_JUMP 0x00080000 // crouch jumping +#define PMF_GOGGLES_ON 0x00100000 // goggles are on +#define PMF_LEANING 0x00200000 // currently leaning + +#define PMF_AUTORELOAD 0x00400000 // autoreloading enabled + +#define PMF_SIAMESETWINS 0x00800000 + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) +#define PMF_ZOOM_FLAGS (PMF_ZOOMED|PMF_ZOOM_LOCKED|PMF_ZOOM_REZOOM|PMF_ZOOM_DEFER_RELOAD) + +// pmove->pm_debounce + +#define PMD_JUMP 0x0001 +#define PMD_ATTACK 0x0002 +#define PMD_FIREMODE 0x0004 +#define PMD_USE 0x0008 +#define PMD_ALTATTACK 0x0010 +#define PMD_GOGGLES 0x0020 + + +#define MAXTOUCH 32 + +typedef struct { + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + int framecount; + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + int useEvent; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + animation_t *animations; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); + +// pmove_pers_t pm_pers; + + int weaponAnimIdx; + char weaponAnim[MAX_QPATH]; + char weaponEndAnim[MAX_QPATH]; +#if _Debug + int isClient; +#endif +} pmove_t; + +extern pmove_t *pm; + +#define SETANIM_TORSO 1 +#define SETANIM_LEGS 2 +#define SETANIM_BOTH SETANIM_TORSO|SETANIM_LEGS//3 + +#define SETANIM_FLAG_NORMAL 0//Only set if timer is 0 +#define SETANIM_FLAG_OVERRIDE 1//Override previous +#define SETANIM_FLAG_HOLD 2//Set the new timer +#define SETANIM_FLAG_RESTART 4//Allow restarting the anim if playing the same one (weapon fires) +#define SETANIM_FLAG_HOLDLESS 8//Set the new timer + + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +//=================================================================================== + + +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum +{ + STAT_HEALTH, + STAT_WEAPONS, // 16 bit fields + STAT_ARMOR, + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_FROZEN, + STAT_GOGGLES, // Which visual enhancing device they have + STAT_GAMETYPE_ITEMS, // Which gametype items they have + STAT_SEED, // seed used to keep weapon firing in sync + STAT_OUTFIT_GRENADE, // indicates which greande is chosen in the outfitting + +} statIndex_t; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// NOTE: may not have more than 16 +typedef enum +{ + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_RANK, // player rank or team rank + PERS_TEAM, // player team + PERS_SPAWN_COUNT, // incremented every respawn + PERS_PLAYEREVENTS, // 16 bits that can be flipped for events + PERS_ATTACKER, // clientnum of last damage inflicter + + PERS_RED_SCORE, // Blue team score + PERS_BLUE_SCORE, // red team score + +} persEnum_t; + + +typedef enum +{ + GOGGLES_NONE, + GOGGLES_NIGHTVISION, + GOGGLES_INFRARED, + GOGGLES_MAX + +} goggleType_t; + +// entityState_t->eFlags +#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD +#define EF_EXPLODE 0x00000002 // ready to explode +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes + +#define EF_PLAYER_EVENT 0x00000008 +#define EF_BOUNCE 0x00000008 // for missiles + +#define EF_BOUNCE_HALF 0x00000010 // bounce and retain half velocity each time +#define EF_BOUNCE_SCALE 0x00000020 // bounces using the bounce scale +#define EF_NODRAW 0x00000040 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000080 // for lightning gun +#define EF_ALT_FIRING 0x00000100 // for alt-fires, mostly for lightning guns though +#define EF_MOVER_STOP 0x00000200 // will push otherwise +#define EF_TALK 0x00000400 // draw a talk balloon +#define EF_CONNECTION 0x00000800 // draw a connection trouble sprite +#define EF_VOTED 0x00001000 // already cast a vote +#define EF_ANGLE_OVERRIDE 0x00002000 // angle coming from the server is absolute +#define EF_PERMANENT 0x00004000 // this entity is permanent and is never updated (sent only in the game state) + // this should only be used in RMG! +#define EF_NOPICKUP 0x00008000 // entity cannot be picked up +#define EF_NOSHADOW 0x00008000 // used for bodies when they are sinking + +#define EF_REDTEAM 0x00010000 // Red team only +#define EF_BLUETEAM 0x00020000 // Blue team only +#define EF_INSKY 0x00040000 // In a sky brush + +#define EF_GOGGLES 0x00080000 // goggles on or not + +#define EF_DUCKED 0x00100000 // ducked? +#define EF_INVULNERABLE 0x00200000 // cant be shot + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum +{ + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + + EV_JUMP, + EV_WATER_FOOTSTEP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LAND, // landed in water + EV_WATER_CLEAR, + + EV_ITEM_PICKUP, // normal item pickups are predictable + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_CHANGE_WEAPON_CANCELLED, + EV_READY_WEAPON, + EV_FIRE_WEAPON, + EV_ALT_FIRE, + + EV_USE, // +Use key + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_PLAY_EFFECT, + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_ENTITY_SOUND, + + EV_GLASS_SHATTER, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + + EV_BULLET_HIT_WALL, + EV_BULLET_HIT_FLESH, + EV_BULLET, // otherEntity is the shooter + + EV_EXPLOSION_HIT_FLESH, + + EV_PAIN, + EV_PAIN_WATER, + EV_OBITUARY, + + EV_DESTROY_GHOUL2_INSTANCE, + + EV_WEAPON_CHARGE, + EV_WEAPON_CHARGE_ALT, + + EV_DEBUG_LINE, + EV_TESTLINE, + EV_STOPLOOPINGSOUND, + + EV_BODY_QUEUE_COPY, + EV_BOTWAYPOINT, + + // Procedural gore event. + EV_PROC_GORE, + + EV_GAMETYPE_RESTART, // gametype restarting + EV_GAME_OVER, // game is over + + EV_GOGGLES, // goggles turning on/off + + EV_WEAPON_CALLBACK, + +} entity_event_t; // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) + +typedef enum +{ + GAME_OVER_TIMELIMIT, + GAME_OVER_SCORELIMIT, + +} game_over_t; + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED +} global_team_sound_t; + + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//--------------------------------------------------------- + +typedef enum +{ + IT_BAD, + IT_WEAPON, // Weapon item + IT_AMMO, // Ammo item + IT_ARMOR, // Armor item + IT_HEALTH, // Healh item + IT_GAMETYPE, // Custom gametype related item + IT_BACKPACK, // replenish backpack item + IT_PASSIVE, // Passive items + +} itemType_t; + +#define MAX_ITEM_MODELS 4 + +typedef struct gitem_s +{ + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + + char *icon; + char *render; + char *pickup_prefix; // an, some, a, the, etc.. + char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use + + int outfittingGroup; + +} gitem_t; + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +gitem_t* BG_FindItem ( const char *pickupName ); +gitem_t* BG_FindClassnameItem ( const char *classname ); +gitem_t* BG_FindWeaponItem ( weapon_t weapon ); +gitem_t* BG_FindGametypeItem ( int index ); +gitem_t* BG_FindGametypeItemByID ( int itemid ); + + +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_TERRAIN) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_PLAYERCLIP|CONTENTS_BODY|CONTENTS_SHOTCLIP) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_TERRAIN|CONTENTS_BODY|CONTENTS_SHOTCLIP) + + +// +// entityState_t->eType +// +typedef enum +{ + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_GRAPPLE, // grapple hooked on wall + ET_BODY, + ET_DAMAGEAREA, + ET_TERRAIN, + + ET_DEBUG_CYLINDER, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + +void BG_AddLadder ( vec3_t absmin, vec3_t absmax, vec3_t fwd ); +int BG_FindLadder ( vec3_t pos ); + +void BG_EvaluateTrajectory ( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta ( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); +void BG_PlayerAngles( vec3_t startAngles, vec3_t legs[3], + vec3_t legsAngles, vec3_t lowerTorsoAngles, vec3_t upperTorsoAngles, vec3_t headAngles, + int leanOffset, int painTime, int painDirection, int currentTime, + animInfo_t* torsoInfo, animInfo_t* legsInfo, + int frameTime, vec3_t velocity, qboolean dead, float movementDir, void *ghoul2 ); +qboolean BG_ParseAnimationFile ( const char *filename, animation_t* animations ); + +float BG_CalculateLeanOffset ( int leanTime ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + +TAnimWeapon *BG_GetWeaponAnim(int weaponAction,playerState_t *ps,int *animIndex); +TNoteTrack *BG_GetWeaponNote( playerState_t* ps, int weapon, int anim, int animChoice, int callbackStep ); + +typedef enum +{ + WACT_READY, + WACT_IDLE, + WACT_FIRE, + WACT_FIRE_END, // Special fire-end action for some weapons. + WACT_ALTFIRE, + WACT_ALTFIRE_END, // Special altfire-end action for some weapons. + WACT_RELOAD, + WACT_ALTRELOAD, + WACT_RELOAD_END, // Special reload-end for M590 shotgun + WACT_PUTAWAY, + WACT_ZOOMIN, + WACT_ZOOMOUT, + WACT_CHARGE, + WACT_ALTCHARGE +} WACT; + + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + + +#define HL_NONE 0x00000000 // 0 + +#define HL_FOOT_RT 0x00000001 +#define HL_FOOT_LT 0x00000002 +#define HL_LEG_UPPER_RT 0x00000004 +#define HL_LEG_UPPER_LT 0x00000008 +#define HL_LEG_LOWER_RT 0x00000010 // 5 + +#define HL_LEG_LOWER_LT 0x00000020 +#define HL_HAND_RT 0x00000040 +#define HL_HAND_LT 0x00000080 +#define HL_ARM_RT 0x00000100 +#define HL_ARM_LT 0x00000200 // 10 + +#define HL_HEAD 0x00000400 +#define HL_WAIST 0x00000800 +#define HL_BACK_RT 0x00001000 +#define HL_BACK_LT 0x00002000 +#define HL_BACK 0x00004000 // 15 + +#define HL_CHEST_RT 0x00008000 +#define HL_CHEST_LT 0x00010000 +#define HL_CHEST 0x00020000 +#define HL_NECK 0x00040000 +#define HL_DEBUG 0x00080000 // 20 + +#define HL_MAX 0x00100000 + +#define HL_DISMEMBERBIT 0x80000000 + +#define GORE_NONE 0x80000000 + +/* +typedef enum +{ + HL_NONE, + + HL_FOOT_RT, + HL_FOOT_LT, + HL_LEG_UPPER_RT, + HL_LEG_UPPER_LT, + HL_LEG_LOWER_RT, + HL_LEG_LOWER_LT, + HL_HAND_RT, + + HL_HAND_LT, + HL_ARM_RT, + HL_ARM_LT, + HL_HEAD, + HL_WAIST, + + HL_BACK_RT, + HL_BACK_LT, + HL_BACK, + HL_CHEST_RT, + HL_CHEST_LT, + + HL_CHEST, + HL_NECK, + + HL_DEBUG +} hitLocation_t; +*/ + +/******************************************************************************* + * + * Dynamic gametype system + * + *******************************************************************************/ + +#define MAX_GAMETYPES 128 +#define MAX_GAMETYPE_PHOTOS 4 + +typedef enum +{ + RT_NORMAL, + RT_DELAYED, + RT_INTERVAL, + RT_NONE, + RT_MAX + +} respawnType_t; + +typedef struct gametypePhoto_s +{ + const char* name; + const char* displayName; + +} gametypePhoto_t; + +typedef struct gametypeData_s +{ + const char* name; + const char* displayName; + const char* script; + const char* description; + + respawnType_t respawnType; + qboolean pickupsDisabled; + qboolean teams; + qboolean showKills; + + int backpack; + + gametypePhoto_t photos[4]; + +} gametypeData_t; + +extern gametypeData_t bg_gametypeData[]; +extern int bg_gametypeCount; + +qboolean BG_BuildGametypeList ( void ); +int BG_FindGametype ( const char* name ); + +/******************************************************************************* + * + * Player model templates + * + *******************************************************************************/ + +#define MAX_OUTFITTING_NAME 64 + +typedef struct goutfitting_s +{ + char name [ MAX_OUTFITTING_NAME ]; + int items [ OUTFITTING_GROUP_MAX ]; + +} goutfitting_t; + +typedef struct SSurfaceList +{ + const char *mName; + + struct SSurfaceList *mNext; + +} TSurfaceList; + +typedef struct SItemTemplate +{ + const char *mName; + const char *mModel; + TSurfaceList *mOnList; + TSurfaceList *mOffList; + + struct SItemTemplate *mNext; + +} TItemTemplate; + +typedef struct SInventoryTemplate +{ + const char *mName; + const char *mBolt; + TItemTemplate *mItem; + + struct SInventoryTemplate *mNext; + + qboolean mOnBack; + + int mBoltIndex; + int mModelIndex; + +} TInventoryTemplate; + +typedef struct SSkinTemplate +{ + const char *mSkin; + TInventoryTemplate *mInventory; + + struct SSkinTemplate *mNext; + +} TSkinTemplate; + +#define MAX_MODEL_SOUNDS 8 +#define MAX_IDENTITIES 256 +#define MAX_OUTFITTINGS 64 +#define MAX_OUTFITTING_GROUPITEM 10 + +typedef struct SModelSounds +{ + const char *mName; + const char *mSounds[MAX_MODEL_SOUNDS]; + int mCount; + + struct SModelSounds *mNext; + +} TModelSounds; + +typedef struct SCharacterTemplate +{ + const char *mName; + const char *mModel; + const char *mParentName; + const char *mFormalName; + int mSoundCount; + qboolean mDeathmatch; + + TInventoryTemplate *mInventory; + TSkinTemplate *mSkins; + TModelSounds *mSounds; + + struct SCharacterTemplate *mParent; + struct SCharacterTemplate *mNext; + +} TCharacterTemplate; + +typedef struct SIdentity +{ + const char *mName; + const char *mTeam; + TCharacterTemplate *mCharacter; + TSkinTemplate *mSkin; + qhandle_t mIcon; + +} TIdentity; + +extern TCharacterTemplate *bg_characterTemplates; +extern TItemTemplate *bg_itemTemplates; +extern TIdentity bg_identities[]; +extern int bg_identityCount; +extern goutfitting_t bg_outfittings[]; +extern int bg_outfittingCount; +extern int bg_outfittingGroups[][10]; +extern char *bg_weaponNames[WP_NUM_WEAPONS]; +extern stringID_table_t bg_animTable [MAX_ANIMATIONS+1]; + +TIdentity* BG_FindIdentity ( const char *identityName ); +TIdentity* BG_FindTeamIdentity ( const char *identityName, int index ); +TCharacterTemplate* BG_FindCharacterTemplate ( const char *name ); +const char* BG_GetModelSound ( const char *identityName, const char *SoundGroup, int index ); +qboolean BG_ParseNPCFiles ( void ); +int BG_ParseSkin ( const char* filename, char* pairs, int pairsSize ); + +qboolean BG_IsWeaponAvailableForOutfitting ( weapon_t weapon, int level ); +void BG_SetAvailableOutfitting ( const char* available ); +void BG_DecompressOutfitting ( const char* compressed, goutfitting_t* outfitting ); +void BG_CompressOutfitting ( goutfitting_t* outfitting, char* compressed, int size ); +int BG_ParseOutfittingTemplates ( qboolean force ); +int BG_FindOutfitting ( goutfitting_t* outfitting); + +/******************************************************************************* + * + * Trap calls that are shared in game, cgame, and user interface and accessed + * by the bg_ code. + * + *******************************************************************************/ + +typedef void *TGenericParser2; +typedef void *TGPGroup; +typedef void *TGPValue; + +// CGenericParser2 (void *) routines +TGenericParser2 trap_GP_Parse ( char **dataPtr, qboolean cleanFirst, qboolean writeable ); +TGenericParser2 trap_GP_ParseFile ( char *fileName, qboolean cleanFirst, qboolean writeable ); +void trap_GP_Clean ( TGenericParser2 GP2 ); +void trap_GP_Delete ( TGenericParser2 *GP2 ); +TGPGroup trap_GP_GetBaseParseGroup ( TGenericParser2 GP2 ); + +// CGPGroup (void *) routines +qboolean trap_GPG_GetName ( TGPGroup GPG, char *Value); +TGPGroup trap_GPG_GetNext ( TGPGroup GPG); +TGPGroup trap_GPG_GetInOrderNext ( TGPGroup GPG); +TGPGroup trap_GPG_GetInOrderPrevious ( TGPGroup GPG); +TGPValue trap_GPG_GetPairs ( TGPGroup GPG); +TGPGroup trap_GPG_GetInOrderPairs ( TGPGroup GPG); +TGPGroup trap_GPG_GetSubGroups ( TGPGroup GPG); +TGPGroup trap_GPG_GetInOrderSubGroups ( TGPGroup GPG); +TGPGroup trap_GPG_FindSubGroup ( TGPGroup GPG, const char *name); +TGPValue trap_GPG_FindPair ( TGPGroup GPG, const char *key); +qboolean trap_GPG_FindPairValue ( TGPGroup GPG, const char *key, const char *defaultVal, char *Value); + +// CGPValue (void *) routines +qboolean trap_GPV_GetName ( TGPValue GPV, char *Value); +TGPValue trap_GPV_GetNext ( TGPValue GPV); +TGPValue trap_GPV_GetInOrderNext ( TGPValue GPV ); +TGPValue trap_GPV_GetInOrderPrevious ( TGPValue GPV ); +qboolean trap_GPV_IsList ( TGPValue GPV ); +qboolean trap_GPV_GetTopValue ( TGPValue GPV, char *Value ); +TGPValue trap_GPV_GetList ( TGPValue GPV ); + +extern int trap_FS_GetFileList ( const char *path, const char *extension, char *listbuf, int bufsize ); + +void *trap_VM_LocalAlloc ( int size ); +void *trap_VM_LocalAllocUnaligned ( int size ); // WARNING!!!! USE WITH CAUTION!!! BEWARE OF DOG!!! +void *trap_VM_LocalTempAlloc( int size ); +void trap_VM_LocalTempFree( int size ); // free must be in opposite order of allocation! +const char *trap_VM_LocalStringAlloc ( const char *source ); + +#endif // __BG_PUBLIC_H__ diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c new file mode 100644 index 0000000..7b78054 --- /dev/null +++ b/code/game/bg_slidemove.c @@ -0,0 +1,352 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 + +qboolean PM_SlideMove( qboolean gravity ) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) + { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + + if ( pml.groundPlane ) + { + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) + { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + } + else + { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) + { + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + + if (trace.allsolid) + { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if (trace.fraction > 0) + { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1) + { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) + { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) + { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) + { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + + if ( i < numplanes ) + { + continue; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) + { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) + { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) + { + if ( j == i ) + { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) + { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) + { + if ( k == i || k == j ) + { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) + { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) + { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) + { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return (qboolean)( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) +{ + trace_t trace; + vec3_t down; + vec3_t up = {0, 0, 1}; + qboolean result = qtrue; + vec3_t start_o; + vec3_t start_v; + vec3_t save_o; + vec3_t save_v; + int stepsize; + float delta; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_SlideMove( gravity ) == 0 ) + { + // we got exactly where we wanted to go on the first try + return; + } + + // Set the standard stepsize + stepsize = STEPSIZE; + + // Add to the step size if we are crouched when jumping + if ( pm->ps->pm_flags & PMF_CROUCH_JUMP ) + { + stepsize += (DEFAULT_VIEWHEIGHT-CROUCH_VIEWHEIGHT); + } + + // Save the origin and velocity in case we have to undo + VectorCopy ( pm->ps->origin, save_o ); + VectorCopy ( pm->ps->velocity, save_v ); + + // First lets see if there is any hope of steping up + pm->maxs[2] -= stepsize; + + VectorCopy ( start_o, pm->ps->origin ); + VectorCopy ( start_v, pm->ps->velocity ); + + pm->ps->origin[2] += stepsize; + + // try the move with the altered hit box + if ( PM_SlideMove( gravity )) + { + } + + // See how far down now + VectorCopy ( pm->ps->origin, down ); + down[2] -= stepsize; + + // Trace it + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + + // Return the players hitbox to normal + pm->maxs[2] += stepsize; + + // No stepping up if you have upward velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || DotProduct(trace.plane.normal, up) < 0.7)) + { + VectorCopy ( save_o, pm->ps->origin ); + VectorCopy ( save_v, pm->ps->velocity ); + return; + } + + if ( trace.allsolid || trace.startsolid ) + { + result = qfalse; + } + else + { + if ( trace.fraction < 1.0 ) + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + + // Now double check not stuck + VectorCopy ( trace.endpos, pm->ps->origin ); + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); + + if ( trace.allsolid || trace.startsolid ) + result = qfalse; + } + + if ( !result ) + { + VectorCopy ( save_o, pm->ps->origin ); + VectorCopy ( save_v, pm->ps->velocity ); + } + + // use the step move + delta = pm->ps->origin[2] - start_o[2]; + + // Shoould we send a step event? + if ( delta > 2 ) + { + if ( delta < 7 ) + { + PM_AddEvent( EV_STEP_4 ); + } + else if ( delta < 11 ) + { + PM_AddEvent( EV_STEP_8 ); + } + else if ( delta < 15 ) + { + PM_AddEvent( EV_STEP_12 ); + } + else + { + PM_AddEvent( EV_STEP_16 ); + } + } + + if ( pm->debugLevel ) + { + Com_Printf("%i:stepped\n", c_pmove); + } +} + diff --git a/code/game/bg_weapons.c b/code/game/bg_weapons.c new file mode 100644 index 0000000..7162dd2 --- /dev/null +++ b/code/game/bg_weapons.c @@ -0,0 +1,1234 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_weapons.c - weapon data loading + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "g_local.h" + +// names as they appear in the SOF2.wpn and inview files +char *bg_weaponNames[WP_NUM_WEAPONS] = +{ + "No Weapon", // WP_NONE, + "Knife", // WP_KNIFE, + "M1911A1", // WP_M1911A1_PISTOL, + "US SOCOM", // WP_US_SOCOM_PISTOL, + "M590", // WP_M590_SHOTGUN, + "Micro Uzi", // WP_MICRO_UZI_SUBMACHINEGUN, + "M3A1", // WP_M3A1_SUBMACHINEGUN, + "USAS-12", // WP_USAS_12_SHOTGUN, + "M4", // WP_M4_ASSAULT_RIFLE, + "AK74", // WP_AK74_ASSAULT_RIFLE, + "MSG90A1", // WP_MSG90A1_SNIPER_RIFLE, + "M60", // WP_M60_MACHINEGUN, + "MM1", // WP_MM1_GRENADE_LAUNCHER, + "RPG7", // WP_RPG7_LAUNCHER, + "M67", // WP_M67_GRENADE, + "M84", // WP_M84_GRENADE, + "F1", // WP_F1_GRENADE, + "L2A2", // WP_L2A2_GRENADE, + "MDN11", // WP_MDN11_GRENADE, + "SMOHG92", // WP_SMOHG92_GRENADE, + "ANM14", // WP_ANM14_GRENADE, + "M15" // WP_M15_GRENADE, +}; + +weaponData_t weaponData[WP_NUM_WEAPONS]; + +char *ammoNames[AMMO_MAX] = +{ + "Knife", // AMMO_KNIFE, + "0.45 ACP", // AMMO_045, + "5.56mm", // AMMO_556, + "9mm", // AMMO_9 , + "12 gauge", // AMMO_12 , + "7.62mm", // AMMO_762, + "40mm grenade", // AMMO_40, + "RPG7", // AMMO_RPG7 + "M15", // AMMO_M15, + "M67", // AMMO_M67, + "M84", // AMMO_M84, + "F1", // AMMO_F1, + "L2A2", // AMMO_L2A2, + "MDN11", // AMMO_MDN11, + "SMOHG92", // AMMO_SMOHG92, + "ANM14" // AMMO_ANM14, +}; + +ammoData_t ammoData[AMMO_MAX]; + +static qboolean BG_ParseAmmoStats(ammo_t ammoNum, void *group) +{ + char tmpStr[256]; + ammoData_t *ammo; // ammo + + ammo = &ammoData[ammoNum]; + memset(ammo, 0, sizeof(ammoData_t)); + + ammo->name = (char*)trap_VM_LocalStringAlloc ( ammoNames[ammoNum] ); + Q_strlwr ( ammo->name ); + + // Get the scale of the gore for this bullet + trap_GPG_FindPairValue(group, "mp_goreScale||goreScale", "1", tmpStr ); + ammo->goreScale = atof ( tmpStr ); + + // Max ammo will be filled in by the weapon parsing + + return qtrue; +} + +qboolean BG_InitAmmoStats(void) +{ + void *GP2, *topGroup, *topSubs; + char name[256]; + int i; + + ammoData[AMMO_NONE].goreScale = 0.0f; + ammoData[AMMO_NONE].name = "none"; + + GP2 = trap_GP_ParseFile("ext_data/sof2.ammo", qtrue, qfalse); + if (!GP2) + { + return qfalse; + } + + topGroup = trap_GP_GetBaseParseGroup(GP2); + topSubs = trap_GPG_GetSubGroups(topGroup); + while(topSubs) + { + trap_GPG_GetName(topSubs, name); + if (Q_stricmp(name, "ammo") == 0) + { + trap_GPG_FindPairValue(topSubs, "name", "", name); + for(i=0;imelee = trap_VM_LocalStringAlloc ( tmpStr ); + } + + trap_GPG_FindPairValue(attacksub, "name", "NONE", attack->name); + trap_GPG_FindPairValue(attacksub, "hudIcon", "NONE", attack->icon); + + trap_GPG_FindPairValue(attacksub, "mp_ammoType||ammoType", "none", tmpStr); + attack->ammoIndex = AMMO_NONE; + for (i = 0; i < AMMO_MAX; i++) + { + if (0 == Q_stricmp(tmpStr, ammoNames[i])) + { + attack->ammoIndex = i; + break; + } + } + +#ifdef _DEBUG + if (AMMO_MAX == i) + { + Com_Printf("BG_ParseWeaponStats: Unknown ammo: %s\n", tmpStr); + } +#endif + + // Parse the weapon animations + trap_GPG_FindPairValue( attacksub, "mp_animFire", "TORSO_ATTACK_PISTOL", tmpStr ); + attack->animFire = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( attacksub, "mp_animFireZoomed", "", tmpStr ); + attack->animFireZoomed = GetIDForString ( bg_animTable, tmpStr ); + + trap_GPG_FindPairValue(attacksub, "mp_range||range", "8192", tmpStr); + attack->rV.range = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_radius||radius", "0", tmpStr); + attack->splashRadius = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_fireDelay||fireDelay", "0", tmpStr); + attack->fireDelay = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_clipSize||clipSize", "0", tmpStr); + attack->clipSize = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_fireAmount||fireAmount", "1", tmpStr); + attack->fireAmount = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_fireFromClip||fireFromClip", "1", tmpStr); + attack->fireFromClip = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_damage||damage", "0", tmpStr); + attack->damage = atoi(tmpStr); + trap_GPG_FindPairValue(attacksub, "mp_inaccuracy||inaccuracy", "0", tmpStr); + attack->inaccuracy = (int)(atof(tmpStr)*1000.0f); + trap_GPG_FindPairValue(attacksub, "mp_maxInaccuracy||maxInaccuracy", "0", tmpStr); + attack->maxInaccuracy = (int)(atof(tmpStr)*1000.0f); + trap_GPG_FindPairValue(attacksub, "mp_gore||gore", "YES", tmpStr); + attack->gore = (Q_stricmp ( tmpStr, "YES" )?qfalse:qtrue); + + trap_GPG_FindPairValue(attacksub,"mp_extraClips", "0", tmpStr ); + attack->extraClips = atoi ( tmpStr ); + + // max ammo is the combination of all guns that share the ammo + ammoData[attack->ammoIndex].max += attack->clipSize * attack->extraClips; + + trap_GPG_FindPairValue(attacksub,"mp_kickAngles||kickAngles", "0 0 0 0 0 0", tmpStr); + sscanf( tmpStr, "%f %f %f %f %f %f", + &attack->minKickAngles[0], + &attack->maxKickAngles[0], + &attack->minKickAngles[1], + &attack->maxKickAngles[1], + &attack->minKickAngles[2], + &attack->maxKickAngles[2] ); + + if (0 == attack->inaccuracy) + { + trap_GPG_FindPairValue(attacksub, "mp_spread||spread", "0", tmpStr); + attack->inaccuracy = atof(tmpStr); + } + trap_GPG_FindPairValue(attacksub, "mp_pellets||pellets", "1", tmpStr); + attack->pellets = atof(tmpStr); + attack->mod = (meansOfDeath_t)weaponNum; + + trap_GPG_FindPairValue(attacksub, "mp_lockFlashToBarrel||lockFlashToBarrel", "true", tmpStr); + if (0 == Q_stricmp(tmpStr, "false")) + { + attack->weaponFlags |= UNLOCK_MUZZLEFLASH; + } + // load effects, sounds + trap_GPG_FindPairValue(attacksub, "muzzleFlash", "", attack->muzzleEffect); + trap_GPG_FindPairValue(attacksub, "3rdPersonMuzzleFlash", "", attack->muzzleEffectInWorld); + trap_GPG_FindPairValue(attacksub, "EjectBone", "", attack->ejectBone); + trap_GPG_FindPairValue(attacksub, "ShellCasingEject", "", attack->shellEject); + trap_GPG_FindPairValue(attacksub, "TracerEffect", "", attack->tracerEffect); + + // Some alt attacks have special bones they need their muzzle flashes attached to + trap_GPG_FindPairValue ( attacksub, "mp_muzzleFlashBone", "", attack->muzzleEffectBone ); + + sub = trap_GPG_FindSubGroup(attacksub, "fireModes"); + if (sub) + { + int i; + + for ( i = 0; i < 5; i ++ ) + { + trap_GPG_FindPairValue ( sub, va("mp_mode%i||mode%i", i, i ), "", tmpStr ); + if ( !tmpStr[0] ) + { + continue; + } + + if (0 == Q_stricmp("single", tmpStr)) + attack->weaponFlags |= (1<weaponFlags |= (1<weaponFlags |= (1<weaponFlags |= (1<weaponFlags |= (1<weaponFlags |= PROJECTILE_FIRE; + + trap_GPG_FindPairValue(sub, "gravity", "1", tmpStr); + if (0 < atof(tmpStr)) + attack->weaponFlags |= PROJECTILE_GRAVITY; + + trap_GPG_FindPairValue(sub, "detonation", "0", tmpStr); + if (0 == Q_stricmp(tmpStr,"timer")) + attack->weaponFlags |= PROJECTILE_TIMED; + + trap_GPG_FindPairValue(sub, "mp_bounce||bounce", "0", tmpStr ); + attack->bounceScale = atof ( tmpStr ); + + switch ( weaponNum ) + { + case WP_ANM14_GRENADE: + // incediary grenade + attack->weaponFlags |= PROJECTILE_DAMAGE_AREA; + break; + + case WP_KNIFE: + if ( attack->weaponFlags & PROJECTILE_GRAVITY ) + { + attack->weaponFlags &= ~PROJECTILE_GRAVITY; + attack->weaponFlags |= PROJECTILE_LIGHTGRAVITY; + } + break; + } + trap_GPG_FindPairValue(sub, "mp_speed||speed", "0", tmpStr); + attack->rV.velocity = atoi(tmpStr); + trap_GPG_FindPairValue(sub, "mp_timer||timer", "10", tmpStr); + attack->projectileLifetime = (int)(atof(tmpStr) * 1000); + + // 'trail' effect + trap_GPG_FindPairValue(sub, "mp_effect||effect", "", attack->tracerEffect); + trap_GPG_FindPairValue(sub, "model", "", attack->missileG2Model); + trap_GPG_FindPairValue(sub, "mp_explosionEffect||explosionEffect", "", attack->explosionEffect); + trap_GPG_FindPairValue(sub, "mp_explosionSound||explosionSound", "", attack->explosionSound); + } + + return qtrue; +} + +static qboolean BG_ParseWeaponStats(weapon_t weaponNum, void *group) +{ + char tmpStr[256]; + weaponData_t *weapon; + + weapon = &weaponData[weaponNum]; + memset(weapon, 0, sizeof(weaponData_t)); + + weapon->classname = bg_weaponNames[weaponNum]; + trap_GPG_FindPairValue(group, "category", "0", tmpStr); + weapon->category = atoi(tmpStr); + + trap_GPG_FindPairValue(group, "safe", "false", tmpStr); + weapon->safe = !Q_stricmp(tmpStr, "true"); + + trap_GPG_FindPairValue(group, "model", "", weapon->worldModel); + + trap_GPG_FindPairValue(group, "menuImage", "", weapon->menuImage); + + // Grab the animations + trap_GPG_FindPairValue( group, "mp_animRaise", "TORSO_RAISE", tmpStr ); + weapon->animRaise = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animDrop", "TORSO_DROP", tmpStr ); + weapon->animDrop = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animIdle", "TORSO_IDLE_PISTOL", tmpStr ); + weapon->animIdle = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animIdleZoomed", "", tmpStr ); + weapon->animIdleZoomed = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animReload", "", tmpStr ); + weapon->animReload = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animReloadStart", "", tmpStr ); + weapon->animReloadStart = GetIDForString ( bg_animTable, tmpStr ); + trap_GPG_FindPairValue( group, "mp_animReloadEnd", "", tmpStr ); + weapon->animReloadEnd = GetIDForString ( bg_animTable, tmpStr ); + + // primary attack + BG_ParseAttackStats ( weaponNum, &weapon->attack[ATTACK_NORMAL], trap_GPG_FindSubGroup(group, "attack") ); + + // alternate attack + BG_ParseAttackStats ( weaponNum, &weapon->attack[ATTACK_ALTERNATE], trap_GPG_FindSubGroup(group, "altattack") ); + + return qtrue; +} + +qboolean BG_InitWeaponStats(void) +{ + void *GP2, *topGroup, *topSubs; + char name[256]; + int i; + + GP2 = trap_GP_ParseFile("ext_data/sof2.wpn", qtrue, qfalse); + if (!GP2) + { + return qfalse; + } + + topGroup = trap_GP_GetBaseParseGroup(GP2); + topSubs = trap_GPG_GetSubGroups(topGroup); + while(topSubs) + { + trap_GPG_GetName(topSubs, name); + if (Q_stricmp(name, "weapon") == 0) + { + trap_GPG_FindPairValue(topSubs, "name", "", name); + for(i=0;imNote); + trap_GPG_FindPairValue(sub, "frame", "-1", name); + current->mFrame = atoi(name); + + last=insert=head; + while(insert) + { + if(current->mFramemFrame) + { + break; + } + last=insert; + insert=insert->mNext; + } + if(insert==head) + { + head=current; + } + else + { + last->mNext=current; + } + current->mNext=insert; + } + + sub = trap_GPG_GetNext(sub); + } + + return head; +} + +static void BG_FindWeaponFrames(TAnimInfoWeapon *animInfo, int choice) +{ + void *group; + int i; + + if (!numWeaponFiles || !animInfo->mAnim[choice]) + { + animInfo->mNumFrames[choice] = -1; + return; + } + + for(i=0;imAnim[choice] ) == 0 ) + { + break; + } + + group = trap_GPG_GetNext ( group ); + } + + if (group) + { + trap_GPG_FindPairValue(group, "startframe", "0", temp); + animInfo->mStartFrame[choice] = atoi(temp); + trap_GPG_FindPairValue(group, "duration", "0", temp); + animInfo->mNumFrames[choice] = atoi(temp); + trap_GPG_FindPairValue(group, "fps", "0", temp); + animInfo->mFPS[choice] = atoi(temp); + animInfo->mNoteTracks[choice] = BG_FindNoteTracks(group); + return; + } + } + + animInfo->mNumFrames[choice] = -1; +} + +static void BG_CloseWeaponFrames(int upTo) +{ + int i; + + for(i=upTo; i < numWeaponFiles; i++) + { + if (weaponFrames[i]) + { + trap_GP_Delete(&weaponFrames[i]); + } + } + + numWeaponFiles = upTo; +} + + +static qboolean BG_ParseAnimGroup(weapon_t weapon, void *animGroup) +{ + void *sub; + char name[256]; + TAnimWeapon *anim; + TAnimInfoWeapon *info; + char value[256]; + int i; + char temp[256]; + + anim = (TAnimWeapon *)trap_VM_LocalAlloc(sizeof(*anim)); + memset(anim, 0, sizeof(*anim)); + + anim->mNext = weaponParseInfo[weapon].mAnimList; + weaponParseInfo[weapon].mAnimList = anim; + + trap_GPG_FindPairValue(animGroup, "name", "", anim->mName); + trap_GPG_FindPairValue(animGroup, "mp_muzzle||muzzle", "", anim->mMuzzle); + + sub = trap_GPG_GetSubGroups(animGroup); + while(sub) + { + trap_GPG_GetName(sub, name); + if (Q_stricmp(name, "info") == 0) + { + info = (TAnimInfoWeapon *)trap_VM_LocalAlloc(sizeof(*info)); + memset(info, 0, sizeof(*info)); + + info->mNext = anim->mInfos; + anim->mInfos = info; + + info->mNumChoices = 0; + trap_GPG_FindPairValue(sub, "name", "", info->mName); + trap_GPG_FindPairValue(sub, "type", "", info->mType); + + // Cache for later + if ( !Q_stricmp ( info->mType, "weaponmodel" ) ) + { + anim->mWeaponModelInfo = info; + } + + // We first look for a multiplayer specific speed. If we don't + // find a valid speed, use the single player speed instead. + trap_GPG_FindPairValue(sub, "mp_speed||speed", "0", temp); + info->mSpeed = atof(temp); + if(!info->mSpeed) + { + trap_GPG_FindPairValue(sub, "mp_speed||speed", "1", temp); + info->mSpeed = atof(temp); + } + trap_GPG_FindPairValue(sub, "lodbias", "0", temp); + info->mLODBias = atoi(temp); + + for(i=0;i<=MAX_WEAPON_ANIM_CHOICES;i++) + { + if (i == 0) + { + strcpy(temp, "anim||animNoLerp"); + } + else + { + Com_sprintf(temp, sizeof(temp), "anim%d||animNoLerp%d", i, i); + } + trap_GPG_FindPairValue(sub, temp, "", value); + if (value[0] && info->mNumChoices < MAX_WEAPON_ANIM_CHOICES) + { + info->mAnim[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1); + strcpy(info->mAnim[info->mNumChoices], value); + + if (i == 0) + { + strcpy(temp, "transition"); + } + else + { + Com_sprintf(temp, sizeof(temp), "transition%d", i); + } + trap_GPG_FindPairValue(sub, temp, "", value); + if (value[0]) + { + info->mTransition[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1); + strcpy(info->mTransition[info->mNumChoices], value); + } + + if (i == 0) + { + strcpy(temp, "end"); + } + else + { + Com_sprintf(temp, sizeof(temp), "end%d", i); + } + trap_GPG_FindPairValue(sub, temp, "", value); + if (value[0]) + { + info->mEnd[info->mNumChoices] = (char *)trap_VM_LocalAlloc(strlen(value)+1); + strcpy(info->mEnd[info->mNumChoices], value); + } + + info->mNumChoices++; + } + } + } + + sub = trap_GPG_GetNext(sub); + } + + return qtrue; +} + +static TBoltonWeapon *BG_ParseBolton(void *boltonGroup) +{ + TBoltonWeapon *bolton; + void *sub; + char temp[256]; + + bolton = (TBoltonWeapon *)trap_VM_LocalAlloc(sizeof(*bolton)); + memset(bolton, 0, sizeof(*bolton)); + + trap_GPG_FindPairValue(boltonGroup, "name", "", bolton->mName); + trap_GPG_FindPairValue(boltonGroup, "model", "", bolton->mModel); + trap_GPG_FindPairValue(boltonGroup, "parent", "", bolton->mParent); + trap_GPG_FindPairValue(boltonGroup, "bolttobone", "", bolton->mBoltToBone); + + trap_GPG_FindPairValue(boltonGroup, "frames", "", temp); + BG_OpenWeaponFrames(temp); + + sub = trap_GPG_FindSubGroup(boltonGroup, "rightside"); + if (sub) + { + BG_BuildSideSurfaceList(sub, "surface", bolton->mRightSide); + } + + sub = trap_GPG_FindSubGroup(boltonGroup, "joint"); + if (sub) + { + trap_GPG_FindPairValue(sub, "bone", "", bolton->mJointBone); + trap_GPG_FindPairValue(sub, "parentBone", "", bolton->mJointParentBone); + trap_GPG_FindPairValue(sub, "fwd", "", bolton->mJointForward); + trap_GPG_FindPairValue(sub, "right", "", bolton->mJointRight); + trap_GPG_FindPairValue(sub, "up", "", bolton->mJointUp); + } + + return bolton; +} + +static qboolean BG_ParseWeaponGroup(TWeaponModel *weapon, void *weaponGroup) +{ + void *sub, *hand; + char name[256]; + TOptionalWeapon *option; + char temp[256]; + + trap_GPG_FindPairValue(weaponGroup, "name", "", weapon->mName); + trap_GPG_FindPairValue(weaponGroup, "model", "", weapon->mModel); + + trap_GPG_FindPairValue(weaponGroup, "frames", "", temp); + BG_OpenWeaponFrames(temp); + + sub = trap_GPG_GetSubGroups(weaponGroup); + while(sub) + { + trap_GPG_GetName(sub, name); + if (Q_stricmp(name, "buffer") == 0) + { + trap_GPG_FindPairValue(sub, "name", "", weapon->mBufferName); + trap_GPG_FindPairValue(sub, "model", "", weapon->mBufferModel); + trap_GPG_FindPairValue(sub, "bolttobone", "", weapon->mBufferBoltToBone); + trap_GPG_FindPairValue(sub, "mp_muzzle||muzzle", "", weapon->mBufferMuzzle); + trap_GPG_FindPairValue(sub, "mp_altmuzzle", "", weapon->mBufferAltMuzzle); + } + else if (Q_stricmp(name, "hands") == 0) + { + hand = trap_GPG_FindSubGroup(sub, "left"); + if (hand) + { + trap_GPG_FindPairValue(hand, "bolttobone", "", weapon->mLeftHandsBoltToBone); + } + hand = trap_GPG_FindSubGroup(sub, "right"); + if (hand) + { + trap_GPG_FindPairValue(hand, "bolttobone", "", weapon->mRightHandsBoltToBone); + } + } + else if (Q_stricmp(name, "bolton") == 0) + { + weapon->mBolton = BG_ParseBolton(sub); + } + else if (Q_stricmp(name, "rightside") == 0) + { + BG_BuildSideSurfaceList(sub, "surface", weapon->mRightSideSurfaces); + } + else if (Q_stricmp(name, "leftside") == 0) + { + BG_BuildSideSurfaceList(sub, "surface", weapon->mLeftSideSurfaces); + } + else if (Q_stricmp(name, "front") == 0) + { + BG_BuildSideSurfaceList(sub, "surface", weapon->mFrontSurfaces); + } + else if (Q_stricmp(name, "optionalpart") == 0) + { + option = (TOptionalWeapon *)trap_VM_LocalAlloc(sizeof(*option)); + memset(option, 0, sizeof(*option)); + trap_GPG_FindPairValue(sub, "name", "", option->mName); + trap_GPG_FindPairValue(sub, "muzzle", "", option->mMuzzle); + BG_BuildSideSurfaceList(sub, "surface", option->mSurfaces); + option->mNext=weapon->mOptionalList; + weapon->mOptionalList=option; +/* + if(weapon->mOptionalList) + { + option->mNext=weapon->mOptionalList; + weapon->mOptionalList=option; + } + else + { + weapon->mOptionalList=option; + } */ + } + + sub = trap_GPG_GetNext(sub); + } + + return qtrue; +} + +static qboolean BG_ParseWeapon(weapon_t weapon, void *group) +{ + void *sub, *soundName, *surfaceCallbackName; + void *soundValue, *surfaceCallbackValue; + char onOffVal[256]; + char name[256]; + int i, j; + TAnimWeapon *anims; + TAnimInfoWeapon *infos; + char temp[256]; + + memset(&weaponParseInfo[weapon], 0, sizeof(TWeaponParseInfo)); + weaponParseInfo[weapon].mName = bg_weaponNames[weapon]; + trap_GPG_FindPairValue(group, "foreshorten", "0.0", temp); + weaponParseInfo[weapon].mForeshorten = atof(temp); + + sub = trap_GPG_GetSubGroups(group); + while(sub) + { + trap_GPG_GetName(sub, name); + + if (Q_stricmp(name, "viewoffset") == 0) + { + trap_GPG_FindPairValue(sub, "forward", "0.0", temp); + weaponParseInfo[weapon].mViewOffset[0] = atof(temp); + trap_GPG_FindPairValue(sub, "right", "0.0", temp); + weaponParseInfo[weapon].mViewOffset[1] = atof(temp); + trap_GPG_FindPairValue(sub, "up", "0.0", temp); + weaponParseInfo[weapon].mViewOffset[2] = atof(temp); + } + else if (Q_stricmp(name, "sounds") == 0) + { + soundName = trap_GPG_GetSubGroups(sub); + for(i=0;soundName && (i < MAX_WEAPON_SOUNDS);i++) + { + trap_GPG_GetName(soundName, weaponParseInfo[weapon].mSoundNames[i]); + soundValue = trap_GPG_GetPairs(soundName); + for(j=0;soundValue && (jmInfos; + while(infos) + { + for(i=0;imNumChoices;i++) + { + BG_FindWeaponFrames(infos, i); + } + infos = infos->mNext; + } + + anims = anims->mNext; + } + + BG_CloseWeaponFrames(numInitialFiles); + + return qtrue; +} + +qboolean BG_ParseInviewFile(void) +{ + void *GP2, *topGroup, *topSubs, *group; + char name[256], temp[256]; + int i; + + GP2 = trap_GP_ParseFile("inview/sof2.inview", qtrue, qfalse); + if (!GP2) + { + return qfalse; + } + + weaponLeftHand[0] = 0; + weaponRightHand[0] = 0; + + topGroup = trap_GP_GetBaseParseGroup(GP2); + topSubs = trap_GPG_GetSubGroups(topGroup); + while(topSubs) + { + trap_GPG_GetName(topSubs, name); + if (Q_stricmp(name, "hands") == 0) + { + group = trap_GPG_FindSubGroup(topSubs, "left"); + if (group) + { + trap_GPG_FindPairValue(group, "model", "", weaponLeftHand); + trap_GPG_FindPairValue(group, "frames", "", temp); + if (BG_OpenWeaponFrames(temp)) + { + numInitialFiles++; + } + } + group = trap_GPG_FindSubGroup(topSubs, "right"); + if (group) + { + trap_GPG_FindPairValue(group, "model", "", weaponRightHand); + trap_GPG_FindPairValue(group, "frames", "", temp); + if (BG_OpenWeaponFrames(temp)) + { + numInitialFiles++; + } + } + } + else if (Q_stricmp(name, "hud") == 0) + { + } + else if (Q_stricmp(name, "weapon") == 0) + { + trap_GPG_FindPairValue(topSubs, "name", "", name); + for(i=0;imName,animKey))) + { + animWeapon=animWeapon->mNext; + (*animIndex)++; + } + if(!animWeapon) + { + return(0); + } + return(animWeapon); +} + +TAnimWeapon *BG_GetInviewAnimFromIndex(int weaponIdx,int animIndex) +{ + TAnimWeapon *animWeapon; + int i=0; + + animWeapon=weaponParseInfo[weaponIdx].mAnimList; + while((animWeapon!=0)&&(i!=animIndex)) + { + animWeapon=animWeapon->mNext; + i++; + } + if(!animWeapon) + { + return(0); + } + return(animWeapon); +} + +TAnimInfoWeapon *BG_GetInviewModelAnim(int weaponIdx,const char *modelKey,const char *animKey) +{ + TAnimWeapon *animWeapon; + TAnimInfoWeapon *animInfoWeapon; + animWeapon=weaponParseInfo[weaponIdx].mAnimList; + while((animWeapon!=0)&&(Q_stricmp(animWeapon->mName,animKey))) + { + animWeapon=animWeapon->mNext; + } + if(!animWeapon) + { + return(0); + } + animInfoWeapon=animWeapon->mInfos; + while((animInfoWeapon!=0)&&(Q_stricmp(animInfoWeapon->mType,modelKey))) + { + animInfoWeapon=animInfoWeapon->mNext; + } + if(!animInfoWeapon) + { + return(0); + } + return(animInfoWeapon); +} + +/* +=============== +BG_WeaponHasAlternateAmmo + +Returns qtrue if the given weapon has ammo for its alternate attack +=============== +*/ +qboolean BG_WeaponHasAlternateAmmo ( int weapon ) +{ + // No valid ammo index means no alternate ammo + if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex == AMMO_NONE ) + { + return qfalse; + } + + // If the alternate attack doesnt deplete ammo then it doesnt use it + if ( !weaponData[weapon].attack[ATTACK_ALTERNATE].fireAmount ) + { + return qfalse; + } + + // If the alternate ammo is the same as the primary ammo then + // the primary is good enough + if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex == + weaponData[weapon].attack[ATTACK_NORMAL].ammoIndex ) + { + return qfalse; + } + + // Yup, alternates have ammo + return qtrue; +} + +/* +=============== +BG_FindFireMode + +Finds the firemode for the given weapon using the given default +=============== +*/ +int BG_FindFireMode ( weapon_t weapon, attackType_t attack, int firemode ) +{ + int i; + + if ( !weapon ) + { + return WP_FIREMODE_NONE; + } + + for ( i=0; i <= WP_FIREMODE_SINGLE; i++ ) + { + if( firemode >= WP_FIREMODE_MAX ) + { + firemode = WP_FIREMODE_NONE + 1; + } + + if( weaponData[weapon].attack[ATTACK_NORMAL].weaponFlags&(1<stats[STAT_WEAPONS] & (1<stats[STAT_OUTFIT_GRENADE] ) + { + continue; + } + } + + if ( weaponData[weapon].attack[ATTACK_NORMAL].ammoIndex == ammoIndex ) + { + ammo += (weaponData[weapon].attack[ATTACK_NORMAL].extraClips + 1) * weaponData[weapon].attack[ATTACK_NORMAL].clipSize; + ammo -= ps->clip[ATTACK_NORMAL][weapon]; + } + + if ( BG_WeaponHasAlternateAmmo ( weapon ) ) + { + if ( weaponData[weapon].attack[ATTACK_ALTERNATE].ammoIndex == ammoIndex ) + { + ammo += (weaponData[weapon].attack[ATTACK_ALTERNATE].extraClips + 1) * weaponData[weapon].attack[ATTACK_ALTERNATE].clipSize; + ammo -= ps->clip[ATTACK_ALTERNATE][weapon]; + } + } + } + + return ammo; +} diff --git a/code/game/bg_weapons.h b/code/game/bg_weapons.h new file mode 100644 index 0000000..83a661c --- /dev/null +++ b/code/game/bg_weapons.h @@ -0,0 +1,369 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_weapons.h - weapons data loading + +#ifndef __BG_WEAPONS_H__ +#define __BG_WEAPONS_H__ + +// means of death +typedef enum +{ + MOD_UNKNOWN, + + // Knife + MOD_KNIFE, + + // Pistols + MOD_M1911A1_PISTOL, + MOD_USSOCOM_PISTOL, + + // Secondarys + MOD_M590_SHOTGUN, + MOD_MICRO_UZI_SUBMACHINEGUN, + MOD_M3A1_SUBMACHINEGUN, + + // Primaries + MOD_USAS_12_SHOTGUN, + MOD_M4_ASSAULT_RIFLE, + MOD_AK74_ASSAULT_RIFLE, + MOD_MSG90A1_SNIPER_RIFLE, + MOD_M60_MACHINEGUN, + MOD_MM1_GRENADE_LAUNCHER, + MOD_RPG7_LAUNCHER, + + // Grenades + MOD_M67_GRENADE, + MOD_M84_GRENADE, + MOD_F1_GRENADE, + MOD_L2A2_GRENADE, + MOD_MDN11_GRENADE, + MOD_SMOHG92_GRENADE, + MOD_ANM14_GRENADE, + MOD_M15_GRENADE, + + MOD_WATER, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TEAMCHANGE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT + +} meansOfDeath_t; + +typedef enum +{ + WP_NONE, + + // Knife + WP_KNIFE, + + // Pistols + WP_M1911A1_PISTOL, + WP_USSOCOM_PISTOL, + + // Secondarys + WP_M590_SHOTGUN, + WP_MICRO_UZI_SUBMACHINEGUN, + WP_M3A1_SUBMACHINEGUN, + + // Primaries + WP_USAS_12_SHOTGUN, + WP_M4_ASSAULT_RIFLE, + WP_AK74_ASSAULT_RIFLE, + WP_MSG90A1, + WP_M60_MACHINEGUN, + WP_MM1_GRENADE_LAUNCHER, + WP_RPG7_LAUNCHER, + + // Grenades + WP_M67_GRENADE, + WP_M84_GRENADE, + WP_F1_GRENADE, + WP_L2A2_GRENADE, + WP_MDN11_GRENADE, + WP_SMOHG92_GRENADE, + WP_ANM14_GRENADE, + WP_M15_GRENADE, + + WP_NUM_WEAPONS +} weapon_t; + +#define WP_DELAYED_CHANGE_BIT (1<<5) + +typedef enum +{ + AMMO_KNIFE, + AMMO_045, + AMMO_556, + AMMO_9 , + AMMO_12 , + AMMO_762, + AMMO_40, + AMMO_RPG7, + AMMO_M15, + AMMO_M67, + AMMO_M84, + AMMO_F1, + AMMO_L2A2, + AMMO_MDN11, + AMMO_SMOHG92, + AMMO_ANM14, + + AMMO_MAX, + + AMMO_NONE, + +} ammo_t; + +#define WP_FIREMODE_NONE 0 +#define WP_FIREMODE_AUTO 1 +#define WP_FIREMODE_BURST 2 +#define WP_FIREMODE_SINGLE 3 +#define WP_FIREMODE_MAX 4 + +#define PROJECTILE_FIRE 0x0010 // projectile NOT bullet +#define PROJECTILE_TIMED 0x0020 // projectile ONLY explodes after time is up +#define PROJECTILE_GRAVITY 0x0040 // projectile obeys gravity +#define PROJECTILE_DAMAGE_AREA 0x0080 // projectile does area damage over time +#define UNLOCK_MUZZLEFLASH 0x0100 // muzzle flash is locked to muzzle bolt by default +#define PROJECTILE_LIGHTGRAVITY 0x0200 // projectile has light gravity + +typedef enum +{ + CAT_NONE = 0, + CAT_KNIFE, + CAT_PISTOL, + CAT_SHOTGUN, + CAT_SUB, + CAT_ASSAULT, + CAT_SNIPER, + CAT_HEAVY, + CAT_GRENADE, + CAT_MAX + +} ECategory; + +typedef struct attackData_s +{ + char name[MAX_QPATH]; + char icon[MAX_QPATH]; + + const char* melee; + + meansOfDeath_t mod; // means of death + int ammoIndex; // Index to proper ammo slot + union + { + int range; // Range of weapon + int velocity; // speed of projectile + } rV; + + int clipSize; // how large is a clip + int fireAmount; // how much ammo to use per shot + int fireFromClip; // 0 = fire from approp. ammo pool, 1 = fire from clip + int damage; // how much damage is done per hit + float inaccuracy; // how inaccurate is weapon + float maxInaccuracy; // maximum lvl of inaccuracy + int pellets; // how many individual 'bullets' are shot with one trigger pull? + int weaponFlags; // which fire modes are available, projectiles timed or impact, .etc + int projectileLifetime; // how long does projectile live (before exploding) + int splashRadius; // how large is splash damage radius + int fireDelay; // Extra delay when firing + qboolean gore; // is gore enabled for this attack? + int extraClips; // Extra clips you get when starting + float bounceScale; // how much something bounces + + vec3_t minKickAngles; + vec3_t maxKickAngles; + + // Names of effects, sounds, models, bones + char muzzleEffect[MAX_QPATH]; + char muzzleEffectBone[MAX_QPATH]; + char muzzleEffectInWorld[MAX_QPATH]; + char tracerEffect[MAX_QPATH]; + char ejectBone[MAX_QPATH]; + char shellEject[MAX_QPATH]; + char explosionSound[MAX_QPATH]; + char explosionEffect[MAX_QPATH]; + char missileG2Model[MAX_QPATH]; + + int animFire; + int animFireZoomed; + +} attackData_t; + +typedef struct weaponData_s +{ + char *classname; // Spawning name + ECategory category; // what group of weapons is this one part of? + qboolean safe; + char worldModel[MAX_QPATH]; // world model + char menuImage[MAX_QPATH]; // names of the icon files + + int animDrop; + int animRaise; + int animIdle; + int animIdleZoomed; + int animReload; + int animReloadStart; + int animReloadEnd; + + attackData_t attack[ATTACK_MAX]; + + +} weaponData_t; + +typedef struct ammoData_s +{ + char *name; // name of ammo + char icon[32]; // Name of ammo icon file + int max; // Max amount player can hold of ammo + float goreScale; + +} ammoData_t; + +extern char *weaponNames[WP_NUM_WEAPONS]; +extern weaponData_t weaponData[WP_NUM_WEAPONS]; +extern char *ammoNames[AMMO_MAX]; +extern ammoData_t ammoData[AMMO_MAX]; + +// Specific weapon information + +#define WP_FIRST_RANGED_WEAPON WP_M1911A1_PISTOL // this is the first weapon for next and prev weapon switching +#define WP_FIRST_MELEE_WEAPON WP_KNIFE +#define MAX_PLAYER_WEAPONS (WP_NUM_WEAPONS-1) // this is the max you can switch to and get with the give all. + +#define MAX_WEAPON_SOUNDS 12 +#define MAX_WEAPON_SOUND_SLOTS 3 + +#define MAX_SIDE_SURFACES 16 + +typedef struct SOptionalWeapon +{ + char mName[MAX_QPATH]; + char mMuzzle[MAX_QPATH]; + char *mSurfaces[MAX_SIDE_SURFACES]; + + struct SOptionalWeapon *mNext; +} TOptionalWeapon; + +typedef struct SBoltonWeapon +{ + char mName[MAX_QPATH]; + char mModel[MAX_QPATH]; + char mParent[MAX_QPATH]; + char mBoltToBone[MAX_QPATH]; + char *mRightSide[MAX_SIDE_SURFACES]; + + char mJointBone[MAX_QPATH]; + char mJointParentBone[MAX_QPATH]; + char mJointForward[10]; + char mJointRight[10]; + char mJointUp[10]; +} TBoltonWeapon; + +typedef struct SNoteTrack +{ + char mNote[64]; + int mFrame; + + struct SNoteTrack *mNext; +} TNoteTrack; + +#define MAX_WEAPON_ANIM_CHOICES 4 + +typedef struct SAnimInfoWeapon +{ + char mName[MAX_QPATH]; + char mType[MAX_QPATH]; + char *mAnim[MAX_WEAPON_ANIM_CHOICES]; + char *mTransition[MAX_WEAPON_ANIM_CHOICES]; + char *mEnd[MAX_WEAPON_ANIM_CHOICES]; + float mSpeed; + int mLODBias; + int mNumChoices; + + int mStartFrame[MAX_WEAPON_ANIM_CHOICES]; + int mNumFrames[MAX_WEAPON_ANIM_CHOICES]; + int mFPS[MAX_WEAPON_ANIM_CHOICES]; + + struct SNoteTrack *mNoteTracks[MAX_WEAPON_ANIM_CHOICES]; + struct SAnimInfoWeapon *mNext; +} TAnimInfoWeapon; + +typedef struct SAnimWeapon +{ + char mName[MAX_QPATH]; + char mMuzzle[MAX_QPATH]; + + struct SAnimInfoWeapon *mInfos; + struct SAnimInfoWeapon *mWeaponModelInfo; // "weaponmodel" info + struct SAnimWeapon *mNext; + +} TAnimWeapon; + +typedef struct SWeaponModel +{ + char mName[MAX_QPATH]; + char mModel[MAX_QPATH]; + char mBufferName[MAX_QPATH]; + char mBufferModel[MAX_QPATH]; + char mBufferBoltToBone[MAX_QPATH]; + char mBufferMuzzle[MAX_QPATH]; + char mBufferAltMuzzle[MAX_QPATH]; + char mLeftHandsBoltToBone[MAX_QPATH]; + char mRightHandsBoltToBone[MAX_QPATH]; + char *mFrontSurfaces[MAX_SIDE_SURFACES], + *mRightSideSurfaces[MAX_SIDE_SURFACES], + *mLeftSideSurfaces[MAX_SIDE_SURFACES]; + + struct SOptionalWeapon *mOptionalList; + struct SBoltonWeapon *mBolton; +} TWeaponModel; + +#define MAX_CALLBACK_SURFACES 4 + +typedef struct SOnOffSurface +{ + char mName[64]; + int mStatus; +} TOnOffSurface; + +typedef struct SSurfaceCallback +{ + char mName[64]; + struct SOnOffSurface mOnOffSurfaces[MAX_CALLBACK_SURFACES]; +} TSurfaceCallback; + +#define MAX_SURFACE_CALLBACKS 2 + +typedef struct SWeaponInfo +{ + char *mName; + float mForeshorten; + vec3_t mViewOffset; + char mSoundNames[MAX_WEAPON_SOUNDS][MAX_QPATH]; + char mSounds[MAX_WEAPON_SOUNDS][MAX_WEAPON_SOUND_SLOTS][MAX_QPATH]; + struct SSurfaceCallback mSurfaceCallbacks[MAX_SURFACE_CALLBACKS]; + struct SAnimWeapon *mAnimList; + struct SWeaponModel mWeaponModel; +} TWeaponParseInfo; + +extern TWeaponParseInfo weaponParseInfo[WP_NUM_WEAPONS]; +extern char weaponLeftHand[MAX_QPATH]; +extern char weaponRightHand[MAX_QPATH]; + +qboolean BG_ParseInviewFile ( void); +TAnimWeapon* BG_GetInviewAnim ( int weaponIdx,const char *animKey,int *animIndex); +TAnimWeapon* BG_GetInviewAnimFromIndex ( int weaponIdx,int animIndex); +TAnimInfoWeapon* BG_GetInviewModelAnim ( int weaponIdx,const char *modelKey,const char *animKey); +qboolean BG_WeaponHasAlternateAmmo ( int weapon ); +int BG_FindFireMode ( weapon_t weapon, attackType_t attack, int firemode ); + +void BG_CalculateBulletEndpoint ( vec3_t muzzlePoint, vec3_t fireAngs, float inaccuracy, float range, vec3_t end, int *seed ); +int BG_GetMaxAmmo ( const playerState_t* ps, int ammoIndex ); + +#endif + diff --git a/code/game/botlib.h b/code/game/botlib.h new file mode 100644 index 0000000..56e58c7 --- /dev/null +++ b/code/game/botlib.h @@ -0,0 +1,508 @@ +// Copyright (C) 2001-2002 Raven Software +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * $Author: Mrelusive $ + * $Revision: 2 $ + * $Modtime: 03/01/00 3:32p $ + * $Date: 03/01/00 3:42p $ + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_USE 0x0000002 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_FORCEPOWER 0x0100000 +#define ACTION_ALT_ATTACK 0x0200000 +/* +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 +*/ + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int gametypeitems; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, char *command); + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_Attack)(int client); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + void (*EA_Alt_Attack)(int client); + void (*EA_ForcePower)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); + int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + int (*PC_LoadGlobalDefines)(const char* filename ); + void (*PC_RemoveAllGlobalDefines) ( void ); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ + diff --git a/code/game/chars.h b/code/game/chars.h new file mode 100644 index 0000000..d6fac95 --- /dev/null +++ b/code/game/chars.h @@ -0,0 +1,124 @@ +// Copyright (C) 2001-2002 Raven Software +// +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Tab Size: 4 (real tabs) +//=========================================================================== + + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/code/game/g_active.c b/code/game/g_active.c new file mode 100644 index 0000000..970f0ca --- /dev/null +++ b/code/game/g_active.c @@ -0,0 +1,1404 @@ +// Copyright (C) 2001-2002 Raven Software +// +// g_active.c -- + +#include "g_local.h" + +void P_SetTwitchInfo(gclient_t *client) +{ + client->ps.painTime = level.time; + client->ps.painDirection ^= 1; +} + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) +{ + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) + { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) + { + // didn't take any damage + return; + } + + if ( count > 255 ) + { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) + { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } + else + { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 255; + client->ps.damageYaw = angles[YAW]/360.0 * 255; + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time)) + { + // don't do more than two pain sounds a second + if ( level.time - client->ps.painTime < 500 ) + { + return; + } + + P_SetTwitchInfo(client); + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // clear totals + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + +/* +============= +P_WorldEffects + +Check for drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) +{ + int waterlevel; + + if ( ent->client->noclip ) + { + // don't need air + ent->client->airOutTime = level.time + 12000; + return; + } + + waterlevel = ent->waterlevel; + + // check for drowning + if ( waterlevel == 3 && (ent->watertype & CONTENTS_WATER)) + { + // if out of air, start drowning + if ( ent->client->airOutTime < level.time) + { + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) + { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + { + ent->damage = 15; + } + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) + { +// G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/pain_death/mullins/drown_dead.wav")); + } + else + { + G_AddEvent ( ent, EV_PAIN_WATER, 0 ); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER, HL_NONE ); + } + } + } + else + { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } +} + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) +{ + ent->client->ps.loopSound = 0; +} + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_IsClientSiameseTwin + +Checks to see if the two clients should never have been separated at birth +============ +*/ +static qboolean G_IsClientSiameseTwin ( gentity_t* ent, gentity_t* ent2 ) +{ + if ( G_IsClientSpectating ( ent->client ) || G_IsClientDead ( ent->client ) ) + { + return qfalse; + } + + if ( G_IsClientSpectating ( ent2->client ) || G_IsClientDead ( ent2->client ) ) + { + return qfalse; + } + + if (ent2->r.currentOrigin[0] + ent2->r.mins[0] > ent->r.currentOrigin[0] + ent->r.maxs[0]) + { + return qfalse; + } + + if (ent2->r.currentOrigin[1] + ent2->r.mins[1] > ent->r.currentOrigin[1] + ent->r.maxs[1]) + { + return qfalse; + } + + if (ent2->r.currentOrigin[2] + ent2->r.mins[2] > ent->r.currentOrigin[2] + ent->r.maxs[2]) + { + return qfalse; + } + + if (ent2->r.currentOrigin[0] + ent2->r.maxs[0] < ent->r.currentOrigin[0] + ent->r.mins[0]) + { + return qfalse; + } + + if (ent2->r.currentOrigin[1] + ent2->r.maxs[1] < ent->r.currentOrigin[1] + ent->r.mins[1]) + { + return qfalse; + } + + if (ent2->r.currentOrigin[2] + ent2->r.maxs[2] < ent->r.currentOrigin[2] + ent->r.mins[2]) + { + return qfalse; + } + + return qtrue; +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) +{ + int i; + int num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins; + vec3_t maxs; + static vec3_t range = { 20, 20, 40 }; + + if ( !ent->client ) + { + return; + } + + // dead clients don't activate triggers! + if ( G_IsClientDead ( ent->client ) ) + { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + // Reset the players can use flag + ent->client->ps.pm_flags &= ~(PMF_CAN_USE); + ent->s.modelindex = 0; + + for ( i=0 ; iclient && hit != ent && !hit->client->siameseTwin && (ent->client->ps.pm_flags & PMF_SIAMESETWINS) ) + { + // See if this client has a twin + if ( !G_IsClientSiameseTwin ( ent, hit ) ) + { + continue; + } + + // About time these twins were separated!! + ent->client->siameseTwin = hit; + hit->client->siameseTwin = ent; + } + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) + { + continue; + } + + if ( !hit->touch && !ent->touch ) + { + continue; + } + + // ignore most entities if a spectator + if ( G_IsClientSpectating ( ent->client ) ) + { + if ( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger) + { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + if ( hit->s.eType == ET_ITEM ) + { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) + { + continue; + } + } + else + { + if ( !trap_EntityContact( mins, maxs, hit ) ) + { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch ) + { + hit->touch (hit, ent, &trace); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) + { + ent->touch( ent, hit, &trace ); + } + } + + // Dont bother looking for twins again unless pmove says so + ent->client->ps.pm_flags &= (~PMF_SIAMESETWINS); +} + + +/* +============ +G_MoverTouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) +{ + int i, num; + float step, stepSize, dist; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs, dir, size, checkSpot; + const vec3_t range = { 40, 40, 52 }; + + // non-moving movers don't hit triggers! + if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) + { + return; + } + + VectorSubtract( ent->r.mins, ent->r.maxs, size ); + stepSize = VectorLength( size ); + if ( stepSize < 1 ) + { + stepSize = 1; + } + + VectorSubtract( ent->r.currentOrigin, oldOrg, dir ); + dist = VectorNormalize( dir ); + for ( step = 0; step <= dist; step += stepSize ) + { + VectorMA( ent->r.currentOrigin, step, dir, checkSpot ); + VectorSubtract( checkSpot, range, mins ); + VectorAdd( checkSpot, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( checkSpot, ent->r.mins, mins ); + VectorAdd( checkSpot, ent->r.maxs, maxs ); + + for ( i=0 ; is.eType != ET_PUSH_TRIGGER ) + { + continue; + } + + if ( hit->touch == NULL ) + { + continue; + } + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) + { + continue; + } + + + if ( !trap_EntityContact( mins, maxs, hit ) ) + { + continue; + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch != NULL ) + { + hit->touch(hit, ent, &trace); + } + } + } +} + +/* +================= +G_UpdatePlayerStateScores + +Update the scores in the playerstate +================= +*/ +void G_UpdatePlayerStateScores ( gentity_t* ent ) +{ + // set the CS_SCORES1/2 configstrings, which will be visible to everyone + if ( level.gametypeData->teams ) + { + ent->client->ps.persistant[PERS_RED_SCORE] = level.teamScores[TEAM_RED]; + ent->client->ps.persistant[PERS_BLUE_SCORE] = level.teamScores[TEAM_BLUE]; + } + else + { + if ( level.numConnectedClients == 0 ) + { + ent->client->ps.persistant[PERS_RED_SCORE] = 0; + ent->client->ps.persistant[PERS_BLUE_SCORE] = 0; + } + else if ( level.numConnectedClients == 1 ) + { + ent->client->ps.persistant[PERS_RED_SCORE] = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + ent->client->ps.persistant[PERS_BLUE_SCORE] = 0; + } + else + { + ent->client->ps.persistant[PERS_RED_SCORE] = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + ent->client->ps.persistant[PERS_BLUE_SCORE] = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) +{ + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) + { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + pm.animations = NULL; + + // perform a pmove + Pmove (&pm); + + G_UpdatePlayerStateScores ( ent ); + + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + // attack button cycles through spectators + if ( client->sess.spectatorState != SPECTATOR_FOLLOW && g_forceFollow.integer ) + { + Cmd_FollowCycle_f( ent, 1 ); + } + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) + { + Cmd_FollowCycle_f( ent, 1 ); + } + else if ( ( client->buttons & BUTTON_ALT_ATTACK ) && ! ( client->oldbuttons & BUTTON_ALT_ATTACK ) ) + { + Cmd_FollowCycle_f( ent, -1 ); + } + else if ( !g_forceFollow.integer && ucmd->upmove > 0 && (client->ps.pm_flags & PMF_FOLLOW) ) + { + G_StopFollowing( ent ); + } +} + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( ! g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + (client->pers.cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) +{ + gclient_t *client; + + client = ent->client; + + // Check so see if the player has moved and if so dont let them change their outfitting + if ( !client->noOutfittingChange && ((level.time - client->respawnTime) > 3000)) + { + vec3_t vel; + + // Check the horizontal velocity for movement + VectorCopy ( client->ps.velocity, vel ); + vel[2] = 0; + if ( VectorLengthSquared ( vel ) > 100 ) + { + client->noOutfittingChange = qtrue; + } + } + + // Forgive voice chats + if ( g_voiceFloodCount.integer && ent->client->voiceFloodCount ) + { + int forgiveTime = 60000 / g_voiceFloodCount.integer; + + client->voiceFloodTimer += msec; + while ( client->voiceFloodTimer >= forgiveTime ) + { + // Forgive one voice chat + client->voiceFloodCount--; + + client->voiceFloodTimer -= forgiveTime; + } + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) +{ + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + + if ( (client->buttons & BUTTON_ATTACK) & ( client->oldbuttons ^ client->buttons ) ) + { + // this used to be an ^1 but once a player says ready, it should stick + client->readyToExit = 1; + } +} + +/* +==================== +G_Use + +use key pressed +==================== +*/ +void G_Use ( gentity_t* ent ) +{ + int i; + int num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins; + vec3_t maxs; + static vec3_t range = { 20, 20, 40 }; + + if ( !ent->client ) + { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i=0 ; iuse ) + { + continue; + } + + // Misstion triggers can be used + if ( !Q_stricmp ( hit->classname, "gametype_trigger" ) ) + { + hit->use ( hit, ent, ent ); + } + } +} + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) +{ + int i; + int event; + gclient_t *client; + vec3_t dir; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) + { + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + } + + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) + { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) + { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + { + int damage; + + damage = client->ps.eventParms[ i & (MAX_PS_EVENTS-1) ]; + damage &= 0x000000ff; + + client->ps.eventParms[ i & (MAX_PS_EVENTS-1) ] = damage; + + if ( ent->s.eType != ET_PLAYER ) + { + break; // not in the player model + } + + if ( g_dmflags.integer & DF_NO_FALLING ) + { + break; + } + + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_NO_ARMOR, MOD_FALLING, HL_NONE ); + break; + } + + case EV_FIRE_WEAPON: + ent->client->noOutfittingChange = qtrue; + ent->client->invulnerableTime = 0; + G_FireWeapon( ent, ATTACK_NORMAL ); + break; + + case EV_ALT_FIRE: + ent->client->noOutfittingChange = qtrue; + ent->client->invulnerableTime = 0; + G_FireWeapon( ent, ATTACK_ALTERNATE ); + break; + + case EV_USE: + G_Use ( ent ); + break; + + default: + break; + } + } + +} + +/* +============== +StuckInOtherClient +============== +*/ +static int StuckInOtherClient(gentity_t *ent) +{ + int i; + gentity_t *ent2; + + ent2 = &g_entities[0]; + for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) + { + if ( ent2 == ent ) + { + continue; + } + + if ( !ent2->inuse ) + { + continue; + } + + if ( !ent2->client ) + { + continue; + } + + if ( ent2->health <= 0 ) + { + continue; + } + + // + if (ent2->r.absmin[0] > ent->r.absmax[0]) + continue; + if (ent2->r.absmin[1] > ent->r.absmax[1]) + continue; + if (ent2->r.absmin[2] > ent->r.absmax[2]) + continue; + if (ent2->r.absmax[0] < ent->r.absmin[0]) + continue; + if (ent2->r.absmax[1] < ent->r.absmin[1]) + continue; + if (ent2->r.absmax[2] < ent->r.absmin[2]) + continue; + return qtrue; + } + return qfalse; +} + +void BotTestSolid(vec3_t origin); + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) +{ + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED) + { + return; + } + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) + { + ucmd->serverTime = level.time + 200; + } + + if ( ucmd->serverTime < level.time - 1000 ) + { + ucmd->serverTime = level.time - 1000; + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) + { + return; + } + + if ( msec > 200 ) + { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) + { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) + { + trap_Cvar_Set("pmove_msec", "33"); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) + { + ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) + { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if ( G_IsClientSpectating ( client ) ) + { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) + { + return; + } + + if ( client->noclip ) + { + client->ps.pm_type = PM_NOCLIP; + } + else if ( client->ps.stats[STAT_HEALTH] <= 0 ) + { + client->ps.pm_type = PM_DEAD; + } + else + { + client->ps.pm_type = PM_NORMAL; + } + + client->ps.gravity = g_gravity.value; + + // set speed + client->ps.speed = g_speed.value; + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + pm.ps = &client->ps; + pm.cmd = *ucmd; + if ( pm.ps->pm_type == PM_DEAD ) + { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else if ( client->siameseTwin ) + { + // Make sure we are still stuck, if so, clip through players. + if ( G_IsClientSiameseTwin ( ent, client->siameseTwin ) ) + { + pm.tracemask = MASK_PLAYERSOLID & ~(CONTENTS_BODY); + } + else + { + // Ok, we arent stuck anymore so we can clear the stuck flag. + client->siameseTwin->client->siameseTwin = NULL; + client->siameseTwin = NULL; + + pm.tracemask = MASK_PLAYERSOLID; + } + } + else if ( ent->r.svFlags & SVF_BOT ) + { + pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; + } + else + { + pm.tracemask = MASK_PLAYERSOLID; + } + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + pm.animations = NULL; + +#if _Debug + pm.isClient=0; +#endif + + VectorCopy( client->ps.origin, client->oldOrigin ); + + Pmove (&pm); + + G_UpdatePlayerStateScores ( ent ); + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) + { + ent->eventTime = level.time; + } + + // See if the invulnerable flag should be removed for this client + if ( ent->client->ps.eFlags & EF_INVULNERABLE ) + { + if ( level.time - ent->client->invulnerableTime >= g_respawnInvulnerability.integer * 1000 ) + { + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + } + } + + if (g_smoothClients.integer) + { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else + { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) + { + client->fireHeld = qfalse; // for grapple + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // Need to cache off the firemodes to the persitant data segment so they + // are maintained across spectating and respawning + memcpy ( ent->client->pers.firemode, ent->client->ps.firemode, sizeof(ent->client->ps.firemode ) ); + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // Update the client animation info + G_UpdateClientAnimations ( ent ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity (ent); + if ( !ent->client->noclip ) + { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + // Update the clients anti-lag history + G_UpdateClientAntiLag ( ent ); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if (ent->client->ps.eventSequence != oldEventSequence) + { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) + { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime ) + { + if ( g_forcerespawn.integer > 0 && + ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) + { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & BUTTON_ATTACK ) + { + respawn( ent ); + } + } + + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); +} + +/* +================== +G_CheckClientTeamkill + +Checks to see whether or not this client should be booted from the server +because they killed too many teammates +================== +*/ +void G_CheckClientTeamkill ( gentity_t* ent ) +{ + if ( !g_teamkillDamageMax.integer || !level.gametypeData->teams || !ent->client->sess.teamkillDamage ) + { + return; + } + // See if they crossed the max team kill damage + else if ( ent->client->sess.teamkillDamage < g_teamkillDamageMax.integer ) + { + // Does the client need forgiving? + if ( ent->client->sess.teamkillForgiveTime ) + { + // Are we in a forgiving mood yet? + if ( level.time > ent->client->sess.teamkillForgiveTime + 60000 ) + { + ent->client->sess.teamkillForgiveTime += 60000; + ent->client->sess.teamkillDamage -= g_teamkillDamageForgive.integer; + } + } + + // All forgivin now? + if ( ent->client->sess.teamkillDamage <= 0 ) + { + ent->client->sess.teamkillDamage = 0; + ent->client->sess.teamkillForgiveTime = 0; + } + + return; + } + + ent->client->sess.teamkillDamage = 0; + ent->client->sess.teamkillForgiveTime = 0; + + // Buh bye + trap_SendConsoleCommand( EXEC_INSERT, va("clientkick \"%d\" \"team killing\"\n", ent->s.number ) ); +} + +/* +================== +G_CheckClientTimeouts + +Checks whether a client has exceded any timeouts and act accordingly +================== +*/ +void G_CheckClientTimeouts ( gentity_t *ent ) +{ + // Only timeout supported right now is the timeout to spectator mode + if ( !g_timeouttospec.integer ) + { + return; + } + + // Can only do spect timeouts on dedicated servers + if ( !g_dedicated.integer ) + { + return; + } + + // Already a spectator, no need to boot them to spectator + if ( ent->client->sess.team == TEAM_SPECTATOR ) + { + return; + } + + // Need to be connected + if ( ent->client->pers.connected != CON_CONNECTED ) + { + return; + } + + // See how long its been since a command was received by the client and if its + // longer than the timeout to spectator then force this client into spectator mode + if ( level.time - ent->client->pers.cmd.serverTime > g_timeouttospec.integer * 1000 ) + { + SetTeam ( ent, "spectator", NULL ); + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) +{ + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) + { + ClientThink_real( ent ); + } +} + +/* +================== +G_RunClient +================== +*/ +void G_RunClient( gentity_t *ent ) +{ + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) + { + return; + } + + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) +{ + gclient_t *cl; + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + { + int clientNum, flags; + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) + { + clientNum = level.follow1; + } + else if ( clientNum == -2 ) + { + clientNum = level.follow2; + } + + if ( clientNum >= 0 ) + { + cl = &level.clients[ clientNum ]; + + if ( cl->pers.connected == CON_CONNECTED && !G_IsClientSpectating ( cl ) ) + { + int count; + int ping; + int respawnTimer; + + count = ent->client->ps.persistant[PERS_SPAWN_COUNT]; + ping = ent->client->ps.ping; + flags = (cl->ps.eFlags & ~(EF_VOTED)) | (ent->client->ps.eFlags & (EF_VOTED)); + respawnTimer = ent->client->ps.respawnTimer; + + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.eFlags = flags; + ent->client->ps.persistant[PERS_SPAWN_COUNT] = count; + ent->client->ps.ping = ping; + ent->client->ps.respawnTimer = respawnTimer; + + return; + } + else + { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) + { + Cmd_FollowCycle_f (ent, 1); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } + else + { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) +{ + clientPersistant_t *pers; + + if ( G_IsClientSpectating ( ent->client ) ) + { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) + { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) + { + return; + } + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) + { + ent->s.eFlags |= EF_CONNECTION; + } + else + { + ent->s.eFlags &= ~EF_CONNECTION; + } + + // FIXME: get rid of ent->health... + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + G_SetClientSound (ent); + + // set the latest infor + if (g_smoothClients.integer) + { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else + { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + + SendPendingPredictableEvents( &ent->client->ps ); + + // set the bit for the reachability area the client is currently in +// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); +// ent->client->areabits[i >> 3] |= 1 << (i & 7); +} + + diff --git a/code/game/g_antilag.c b/code/game/g_antilag.c new file mode 100644 index 0000000..985ce32 --- /dev/null +++ b/code/game/g_antilag.c @@ -0,0 +1,319 @@ +// Copyright (C) 2000-2001 Raven Software, Inc. +// +// g_antilag.c -- handles server side anti-lag + +#include "g_local.h" + + +/* +================ +G_UpdateClientAntiLag +================ +*/ +void G_UpdateClientAntiLag ( gentity_t* ent ) +{ + int head; + int newtime; + + head = ent->client->antilagHead; + + // If on a new frame snap the head up to the end of the last frame and + // add a new head + if ( ent->client->antilag[head].leveltime < level.time ) + { + ent->client->antilag[head].time = level.previousTime; + + // Move to the next position + if ( (++ent->client->antilagHead) > MAX_ANTILAG ) + { + ent->client->antilagHead = 0; + } + + head = ent->client->antilagHead; + } + + // Bots only move once per frame + if ( ent->r.svFlags & SVF_BOT ) + { + newtime = level.time; + } + else + { + // calculate the actual server time + newtime = level.previousTime + trap_Milliseconds() - level.frameStartTime; + + if ( newtime > level.time ) + { + newtime = level.time; + } + else if ( newtime <= level.previousTime ) + { + newtime = level.previousTime + 1; + } + } + + // Copy the clients current state into the antilag cache + ent->client->antilag[head].leveltime = level.time; + ent->client->antilag[head].time = newtime; + VectorCopy ( ent->r.currentOrigin, ent->client->antilag[head].rOrigin ); + VectorCopy ( ent->r.currentAngles, ent->client->antilag[head].rAngles ); + VectorCopy ( ent->r.mins, ent->client->antilag[head].mins ); + VectorCopy ( ent->r.maxs, ent->client->antilag[head].maxs ); + + VectorCopy ( ent->client->ghoulLegsAngles, ent->client->antilag[head].legsAngles ); + VectorCopy ( ent->client->ghoulLowerTorsoAngles, ent->client->antilag[head].lowerTorsoAngles ); + VectorCopy ( ent->client->ghoulUpperTorsoAngles, ent->client->antilag[head].upperTorsoAngles ); + VectorCopy ( ent->client->ghoulHeadAngles, ent->client->antilag[head].headAngles ); + + ent->client->antilag[head].legsAnim = ent->s.legsAnim; + ent->client->antilag[head].torsoAnim = ent->s.torsoAnim; + ent->client->antilag[head].pm_flags = ent->client->ps.pm_flags; + ent->client->antilag[head].leanTime = ent->client->ps.leanTime; +} + +/* +================ +G_UndoClientAntiLag +================ +*/ +void G_UndoClientAntiLag ( gentity_t* ent ) +{ + // If the client isnt already in the past then + // dont bother doing anything + if ( ent->client->antilagUndo.leveltime != level.time ) + return; + + // Move the client back into reality by moving over the undo information + VectorCopy ( ent->client->antilagUndo.rOrigin, ent->r.currentOrigin ); + VectorCopy ( ent->client->antilagUndo.rAngles, ent->r.currentAngles ); + VectorCopy ( ent->client->antilagUndo.mins, ent->r.mins ); + VectorCopy ( ent->client->antilagUndo.maxs, ent->r.maxs ); + + VectorCopy ( ent->client->antilagUndo.legsAngles, ent->client->ghoulLegsAngles ); + VectorCopy ( ent->client->antilagUndo.lowerTorsoAngles, ent->client->ghoulLowerTorsoAngles ); + VectorCopy ( ent->client->antilagUndo.upperTorsoAngles, ent->client->ghoulUpperTorsoAngles ); + VectorCopy ( ent->client->antilagUndo.headAngles, ent->client->ghoulHeadAngles ); + + ent->s.legsAnim = ent->client->antilagUndo.legsAnim; + ent->s.torsoAnim = ent->client->antilagUndo.torsoAnim; + ent->client->ps.pm_flags = ent->client->antilagUndo.pm_flags; + ent->client->ps.leanTime = ent->client->antilagUndo.leanTime; + + // Mark the undo information so it cant be used again + ent->client->antilagUndo.leveltime = 0; +} + +/* +================ +G_ApplyClientAntiLag +================ +*/ +void G_ApplyClientAntiLag ( gentity_t* ent, int time ) +{ + float lerp; + int from; + int to; + + // Find the two pieces of history information that sandwitch the + // time we are looking for + from = ent->client->antilagHead; + to = ent->client->antilagHead; + do + { + if ( ent->client->antilag[from].time <= time ) + { + break; + } + + to = from; + from--; + + if ( from < 0 ) + { + from = MAX_ANTILAG - 1; + } + } + while ( from != ent->client->antilagHead ); + + // If the from is equal to the to then there wasnt even + // one piece of information worth using so just use the current time frame + if ( from == to ) + { + return; + } + + // Save the undo information if its not already saved + if ( ent->client->antilagUndo.leveltime != level.time ) + { + // Save the undo information + ent->client->antilagUndo.leveltime = level.time; + + VectorCopy ( ent->r.currentOrigin, ent->client->antilagUndo.rOrigin ); + VectorCopy ( ent->r.currentAngles, ent->client->antilagUndo.rAngles ); + VectorCopy ( ent->r.mins, ent->client->antilagUndo.mins ); + VectorCopy ( ent->r.maxs, ent->client->antilagUndo.maxs ); + + VectorCopy ( ent->client->ghoulLegsAngles, ent->client->antilagUndo.legsAngles ); + VectorCopy ( ent->client->ghoulLowerTorsoAngles, ent->client->antilagUndo.lowerTorsoAngles ); + VectorCopy ( ent->client->ghoulUpperTorsoAngles, ent->client->antilagUndo.upperTorsoAngles ); + VectorCopy ( ent->client->ghoulHeadAngles, ent->client->antilagUndo.headAngles ); + + ent->client->antilagUndo.legsAnim = ent->s.legsAnim; + ent->client->antilagUndo.torsoAnim = ent->s.torsoAnim; + ent->client->antilagUndo.pm_flags = ent->client->ps.pm_flags; + ent->client->antilagUndo.leanTime = ent->client->ps.leanTime; + } + + // If the best history found was the last in the list then + // dont lerp, just use the last one + if ( from == ent->client->antilagHead ) + { + VectorCopy ( ent->client->antilag[to].rOrigin, ent->r.currentOrigin ); + VectorCopy ( ent->client->antilag[to].rAngles, ent->r.currentAngles ); + VectorCopy ( ent->client->antilag[to].mins, ent->r.maxs ); + VectorCopy ( ent->client->antilag[to].maxs, ent->r.mins ); + + VectorCopy ( ent->client->antilag[to].legsAngles, ent->client->ghoulLegsAngles ); + VectorCopy ( ent->client->antilag[to].lowerTorsoAngles, ent->client->ghoulLowerTorsoAngles ); + VectorCopy ( ent->client->antilag[to].upperTorsoAngles, ent->client->ghoulUpperTorsoAngles ); + VectorCopy ( ent->client->antilag[to].headAngles, ent->client->ghoulHeadAngles ); + + ent->s.legsAnim = ent->client->antilag[to].legsAnim; + ent->s.torsoAnim = ent->client->antilag[to].torsoAnim; + ent->client->ps.pm_flags = ent->client->antilag[to].pm_flags; + ent->client->ps.leanTime = ent->client->antilag[to].leanTime; + } + else + { + // Calculate the lerp value to use for the vectors + lerp = (float)(time - ent->client->antilag[from].time) / (float)(ent->client->antilag[to].time - ent->client->antilag[from].time); + + // Lerp all the vectors between the before and after history information + LerpVector ( ent->client->antilag[from].rOrigin, ent->client->antilag[to].rOrigin, lerp, ent->r.currentOrigin ); + LerpVector ( ent->client->antilag[from].rAngles, ent->client->antilag[to].rAngles, lerp, ent->r.currentAngles ); + LerpVector ( ent->client->antilag[from].maxs, ent->client->antilag[to].maxs, lerp, ent->r.maxs ); + LerpVector ( ent->client->antilag[from].mins, ent->client->antilag[to].mins, lerp, ent->r.mins ); + + LerpVector ( ent->client->antilag[from].legsAngles, ent->client->antilag[to].legsAngles, lerp, ent->client->ghoulLegsAngles ); + LerpVector ( ent->client->antilag[from].lowerTorsoAngles, ent->client->antilag[to].lowerTorsoAngles, lerp, ent->client->ghoulLowerTorsoAngles ); + LerpVector ( ent->client->antilag[from].upperTorsoAngles, ent->client->antilag[to].upperTorsoAngles, lerp, ent->client->ghoulUpperTorsoAngles ); + LerpVector ( ent->client->antilag[from].headAngles, ent->client->antilag[to].headAngles, lerp, ent->client->ghoulHeadAngles ); + + ent->client->ps.leanTime = ent->client->antilag[from].leanTime + (ent->client->antilag[from].leanTime-ent->client->antilag[to].leanTime) * lerp; + + ent->s.legsAnim = ent->client->antilag[to].legsAnim; + ent->s.torsoAnim = ent->client->antilag[to].torsoAnim; + ent->client->ps.pm_flags = ent->client->antilag[to].pm_flags; + } +} + +/* +================ +G_UndoAntiLag +================ +*/ +void G_UndoAntiLag ( void ) +{ + int i; + + // Undo all history + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t* other = &g_entities[level.sortedClients[i]]; + + if ( other->client->pers.connected != CON_CONNECTED ) + { + continue; + } + + // Skip clients that are spectating + if ( G_IsClientSpectating ( other->client ) || G_IsClientDead ( other->client ) ) + { + continue; + } + + if ( other->r.svFlags & SVF_DOUBLED_BBOX ) + { + // Put the hitbox back the way it was + other->r.maxs[0] /= 2; + other->r.maxs[1] /= 2; + other->r.mins[0] /= 2; + other->r.mins[1] /= 2; + + other->r.svFlags &= (~SVF_DOUBLED_BBOX); + } + + G_UndoClientAntiLag ( other ); + + // Relink the entity into the world + trap_LinkEntity ( other ); + } +} + +/* +================ +G_ApplyAntiLag +================ +*/ +void G_ApplyAntiLag ( gentity_t* ref, qboolean enlargeHitBox ) +{ + int i; + int reftime; + + // Figure out the reference time based on the reference clients server time + reftime = ref->client->pers.cmd.serverTime; + if ( reftime > level.time ) + { + reftime = level.time; + } + + // Move all the clients back into the reference clients time frame. + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t* other = &g_entities[level.sortedClients[i]]; + + if ( other->client->pers.connected != CON_CONNECTED ) + { + continue; + } + + // Skip the reference client + if ( other == ref ) + { + continue; + } + + // Skip entities not in use + if ( !other->inuse ) + { + continue; + } + + // Skip clients that are spectating + if ( G_IsClientSpectating ( other->client ) || G_IsClientDead ( other->client ) ) + { + continue; + } + + // Dont bring them back in time unless requested + if ( !(ref->r.svFlags & SVF_BOT) & ref->client->pers.antiLag ) + { + // Apply the antilag to this player + G_ApplyClientAntiLag ( other, reftime ); + } + + if ( enlargeHitBox ) + { + // Adjust the hit box to account for hands and such + // that are sticking out of the normal bounding box + other->r.maxs[0] *= 2; + other->r.maxs[1] *= 2; + other->r.mins[0] *= 2; + other->r.mins[1] *= 2; + other->r.svFlags |= SVF_DOUBLED_BBOX; + } + + // Relink the entity into the world + trap_LinkEntity ( other ); + } +} diff --git a/code/game/g_bot.c b/code/game/g_bot.c new file mode 100644 index 0000000..1799705 --- /dev/null +++ b/code/game/g_bot.c @@ -0,0 +1,1082 @@ +// Copyright (C) 2001-2002 Raven Software +// +// g_bot.c + +#include "g_local.h" + + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} + + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( const char *buf, int max, char *infos[] ) { + const char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( &buf, qfalse ); + if ( !token[0] ) + { + Info_SetValueForKey( info, key, "" ); + } + else + { + Info_SetValueForKey( info, key, token ); + } + } + //NOTE: extra space for arena number + infos[count] = trap_VM_LocalAlloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +void G_LoadArenas( void ) +{ + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) + { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } + +#ifdef _DEBUG + Com_Printf ( "%i arenas parsed\n", g_numArenas ); +#endif + + for( n = 0; n < g_numArenas; n++ ) + { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +} + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) +{ + int n; + + for( n = 0; n < g_numArenas; n++ ) + { + if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) + { + return g_arenaInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_DoesMapSupportGametype + +determines whether or not the current map supports the given gametype +=============== +*/ +qboolean G_DoesMapSupportGametype ( const char* gametype ) +{ + char mapname[MAX_QPATH]; + const char* info; + const char* type; + char* token; + + // Figure out the current map name first + if ( RMG.integer ) + { + Com_sprintf ( mapname, MAX_QPATH, "*random" ); + } + else + { + trap_Cvar_VariableStringBuffer ( "mapname", mapname, MAX_QPATH ); + } + + // Get the arena info for the current map + info = G_GetArenaInfoByMap ( mapname ); + if ( !info ) + { + // If they dont have an area file for this map then + // just assume it supports all gametypes + return qtrue; + } + + // Get the supported gametypes + type = Info_ValueForKey( info, "type" ); + + while ( 1 ) + { + token = COM_Parse ( &type ); + if ( !token || !*token ) + { + break; + } + + if ( Q_stricmp ( gametype, token ) == 0 ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) +{ + int i; + int n; + int num; + float skill; + char netname[36]; + char *value; + char *teamstr; + gclient_t *cl; + + num = 0; + + for ( n = 0; n < g_numBots ; n++ ) + { + value = Info_ValueForKey( g_botInfos[n], "name" ); + + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) + { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) + { + continue; + } + + if ( team >= 0 && cl->sess.team != team ) + { + continue; + } + + if ( !Q_stricmp( value, cl->pers.netname ) ) + { + break; + } + } + + if (i >= g_maxclients.integer) + { + num++; + } + } + + num = random() * num; + + for ( n = 0; n < g_numBots ; n++ ) + { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) + { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) + { + continue; + } + + if ( team >= 0 && cl->sess.team != team ) + { + continue; + } + + if ( !Q_stricmp( value, cl->pers.netname ) ) + { + break; + } + } + + if (i >= g_maxclients.integer) + { + num--; + + if (num <= 0) + { + skill = trap_Cvar_VariableValue( "g_botSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) +{ + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) + { + cl = level.clients + i; + + if ( cl->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) + { + continue; + } + + if ( team >= 0 && cl->sess.team != team ) + { + continue; + } + + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + trap_SendConsoleCommand( EXEC_INSERT, va("kick \"%s\"\n", netname) ); + return qtrue; + } + + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) +{ + int i; + int num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) + { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) + { + continue; + } + + if ( team >= 0 && cl->sess.team != team ) + { + continue; + } + + num++; + } + + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) +{ + int i; + int n; + int num; + gclient_t *cl; + + num = 0; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) + { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) + { + continue; + } + + if ( team >= 0 && cl->sess.team != team ) + { + continue; + } + + num++; + } + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) + { + if( !botSpawnQueue[n].spawnTime ) + { + continue; + } + + if ( botSpawnQueue[n].spawnTime > level.time ) + { + continue; + } + + num++; + } + + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) +{ + int minplayers; + int humanplayers; + int botplayers; + static int checkminimumplayers_time; + + if (level.intermissiontime) + { + return; + } + + // only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) + { + return; + } + + checkminimumplayers_time = level.time; + trap_Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + + if (minplayers <= 0) + { + return; + } + + if ( level.gametypeData->teams ) + { + if (minplayers >= g_maxclients.integer / 2) + { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + + // + if (humanplayers + botplayers < minplayers) + { + G_AddRandomBot( TEAM_RED ); + } + else if (humanplayers + botplayers > minplayers && botplayers) + { + G_RemoveRandomBot( TEAM_RED ); + } + + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + + // + if (humanplayers + botplayers < minplayers) + { + G_AddRandomBot( TEAM_BLUE ); + } + else if (humanplayers + botplayers > minplayers && botplayers) + { + G_RemoveRandomBot( TEAM_BLUE ); + } + } + else + { + if (minplayers >= g_maxclients.integer) + { + minplayers = g_maxclients.integer-1; + } + + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + + if (humanplayers + botplayers < minplayers) + { + G_AddRandomBot( TEAM_FREE ); + } + else if (humanplayers + botplayers > minplayers && botplayers) + { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) +{ + int n; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) + { + if( !botSpawnQueue[n].spawnTime ) + { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) + { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum ); + botSpawnQueue[n].spawnTime = 0; + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) +{ + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) + { + if( !botSpawnQueue[n].spawnTime ) + { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + Com_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + ClientBegin( clientNum ); +} + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.personalityfile, Info_ValueForKey( userinfo, "personality" ), sizeof(settings.personalityfile) ); + settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) +{ + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *identity; + char userinfo[MAX_INFO_STRING]; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + Com_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "identity"; + identity = Info_ValueForKey( botinfo, key ); + if ( !*identity ) + { + identity = "mullinsjungle"; + } + Info_SetValueForKey( userinfo, key, identity ); + + s = Info_ValueForKey(botinfo, "personality"); + if (!*s ) + { + Info_SetValueForKey( userinfo, "personality", "botfiles/default.jkb" ); + } + else + { + Info_SetValueForKey( userinfo, "personality", s ); + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + Com_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + Com_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot settings + if( !team || !*team || !Q_stricmp ( team, "auto" )) + { + if( level.gametypeData->teams ) + { + if( PickTeam(clientNum) == TEAM_RED) + { + team = "red"; + } + else + { + team = "blue"; + } + } + else + { + team = "red"; + } + } +// Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if ( level.gametypeData->teams ) + { + if (team && Q_stricmp(team, "red") == 0) + { + bot->client->sess.team = TEAM_RED; + } + else if (team && Q_stricmp(team, "blue") == 0) + { + bot->client->sess.team = TEAM_BLUE; + } + else + { + bot->client->sess.team = PickTeam( -1 ); + } + } + + if( delay == 0 ) { + ClientBegin( clientNum ); + return; + } + + AddBotToSpawnQueue( clientNum, delay ); +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + float skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } + else { + delay = atoi( string ); + } + + // alternative name + trap_Argv( 5, altname, sizeof( altname ) ); + + G_AddBot( name, skill, team, delay, altname ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && trap_Cvar_VariableIntegerValue( "cl_running" ) ) + { + trap_SendServerCommand( -1, "loaddeferred\n" ); + } +} + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char personality[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model personality funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "UnnamedPlayer"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + strcpy(personality, Info_ValueForKey( g_botInfos[i], "personality")); + if (!*personality ) { + strcpy(personality, "botfiles/default.jkb"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, personality, funname)); + } +} + + +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + + skill = trap_Cvar_VariableValue( "g_botSkill" ); + if( skill < 2 ) + { + trap_Cvar_Set( "g_botSkill", "2" ); + skill = 2; + } + else if ( skill > 5 ) + { + trap_Cvar_Set( "g_botSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM, 0.0, 0.0 ); + if( *botsFile.string ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + //G_LoadBotsFromFile("scripts/bots.txt"); + G_LoadBotsFromFile("botfiles/bots.txt"); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } + trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +//rww - pd +void LoadPath_ThisLevel(void); +//end rww + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) +{ + G_LoadBots(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO, 0.0, 0.0 ); + + //rww - new bot route stuff + LoadPath_ThisLevel(); +} diff --git a/code/game/g_client.c b/code/game/g_client.c new file mode 100644 index 0000000..98b6be6 --- /dev/null +++ b/code/game/g_client.c @@ -0,0 +1,1841 @@ +// Copyright (C) 2001-2002 Raven Software +// +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -46}; +static vec3_t playerMaxs = {15, 15, 48}; + + +/* +================ +G_AddClientSpawn + +adds a spawnpoint to the spawnpoint array using the given entity for +origin and angles as well as the team for filtering teams. +================ +*/ +void G_AddClientSpawn ( gentity_t* ent, team_t team ) +{ + static vec3_t mins = {-15,-15,-45}; + static vec3_t maxs = {15,15,46}; + vec3_t end; + trace_t tr; + + // Drop it to the ground, and if it starts solid just throw it out + VectorCopy ( ent->s.origin, end ); + end[2] -= 1024; + + tr.fraction = 0.0f; + trap_Trace ( &tr, ent->s.origin, mins, maxs, end, ent->s.number, MASK_SOLID ); + + // We are only looking for terrain collisions at this point + if ( tr.contents & CONTENTS_TERRAIN ) + { + // If its in the ground then throw it awway + if ( tr.startsolid ) + { + return; + } + // Drop it down to the ground now + else if ( tr.fraction < 1.0f && tr.fraction > 0.0f ) + { + VectorCopy ( tr.endpos, ent->s.origin ); + ent->s.origin[2] += 1.0f; + tr.startsolid = qfalse; + } + } + + if ( tr.startsolid ) + { + Com_Printf ( S_COLOR_YELLOW "WARNING: gametype_player starting in solid at %.2f,%.2f,%.2f\n", ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] ); + } + + level.spawns[level.spawnCount].team = team; + + // Release the entity and store the spawn in its own array + VectorCopy ( ent->s.origin, level.spawns[level.spawnCount].origin ); + VectorCopy ( ent->s.angles, level.spawns[level.spawnCount].angles ); + + // Increase the spawn count + level.spawnCount++; +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -46) (16 16 48) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) +{ + // Cant take any more spawns!! + if ( level.spawnCount >= MAX_SPAWNS ) + { + G_FreeEntity ( ent ); + return; + } + + G_AddClientSpawn ( ent, TEAM_FREE ); + + G_FreeEntity ( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -46) (16 16 48) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) +{ +} + +/* +================ +G_SpotWouldTelefrag +================ +*/ +qboolean G_SpotWouldTelefrag( gspawn_t* spawn ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spawn->origin, playerMins, mins ); + VectorAdd( spawn->origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient) + { + if ( G_IsClientSpectating ( hit->client ) ) + { + continue; + } + + if ( G_IsClientDead ( hit->client ) ) + { + continue; + } + + return qtrue; + } + } + + return qfalse; +} + +/* +================ +G_SelectRandomSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +gspawn_t* G_SelectRandomSpawnPoint ( team_t team ) +{ + int i; + int count; + int tfcount; + gspawn_t *spawns[MAX_SPAWNS]; + gspawn_t *tfspawns[MAX_SPAWNS]; + + count = 0; + tfcount = 0; + + for ( i = 0; i < level.spawnCount; i ++ ) + { + gspawn_t* spawn = &level.spawns[i]; + + if ( team != -1 && team != spawn->team ) + { + continue; + } + + if ( G_SpotWouldTelefrag( spawn ) ) + { + tfspawns[tfcount++] = spawn; + continue; + } + + spawns[ count++ ] = spawn; + } + + // no spots that won't telefrag so just pick one that will + if ( !count ) + { + // No telefrag spots, just return NULL since there is no more to find + if ( !tfcount ) + { + return NULL; + } + + // telefrag someone + return tfspawns[ rand() % tfcount ]; + } + + return spawns[ rand() % count ]; +} + +/* +=========== +G_SelectRandomSafeSpawnPoint + +Select a random spawn point that is safe for the client to spawn at. A safe spawn point +is one that is at least a certain distance from another client. +============ +*/ +gspawn_t* G_SelectRandomSafeSpawnPoint ( team_t team, float safeDistance ) +{ + gspawn_t* spawns[MAX_SPAWNS]; + float safeDistanceSquared; + int count; + int i; + + // Square the distance for faster comparisons + safeDistanceSquared = safeDistance * safeDistance; + + // Build a list of spawns + for ( i = 0, count = 0; i < level.spawnCount; i ++ ) + { + gspawn_t* spawn = &level.spawns[i]; + int j; + + // Ensure the team matches + if ( team != -1 && team != spawn->team ) + { + continue; + } + + // Make sure this spot wont kill another player + if ( G_SpotWouldTelefrag( spawn ) ) + { + continue; + } + + // Loop through connected clients + for ( j = 0; j < level.numConnectedClients && count < MAX_SPAWNS; j ++ ) + { + gentity_t* other = &g_entities[level.sortedClients[j]]; + vec3_t diff; + + if ( other->client->pers.connected != CON_CONNECTED ) + { + continue; + } + + // Skip clients that are spectating or dead + if ( G_IsClientSpectating ( other->client ) || G_IsClientDead ( other->client ) ) + { + continue; + } + + // on safe team, dont count this guy + if ( level.gametypeData->teams && team == other->client->sess.team ) + { + continue; + } + + VectorSubtract ( other->r.currentOrigin, spawn->origin, diff ); + + // Far enough away to qualify + if ( VectorLengthSquared ( diff ) < safeDistanceSquared ) + { + break; + } + } + + // If we didnt go through the whole list of clients then we must + // have hit one that was too close. But if we did go through teh whole + // list then this spawn point is good to go + if ( j >= level.numConnectedClients ) + { + spawns[count++] = spawn; + } + } + + // Nothing found, try it at half the safe distance + if ( !count ) + { + // Gotta stop somewhere + if ( safeDistance / 2 < 250 ) + { + return G_SelectRandomSpawnPoint ( team ); + } + else + { + return G_SelectRandomSafeSpawnPoint ( team, safeDistance / 2 ); + } + } + + // Spawn them at one of the spots + return spawns[ rand() % count ]; +} + +/* +=========== +G_SelectSpectatorSpawnPoint +============ +*/ +gspawn_t* G_SelectSpectatorSpawnPoint( void ) +{ + static gspawn_t spawn; + + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, spawn.origin ); + VectorCopy( level.intermission_angle, spawn.angles ); + + return &spawn; +} + +/* +=============== +G_InitBodyQueue +=============== +*/ +void G_InitBodyQueue (void) +{ + gentity_t *ent; + int max; + + if ( level.gametypeData->respawnType == RT_NONE ) + { + level.bodySinkTime = 0; + max = BODY_QUEUE_SIZE_MAX; + } + else + { + level.bodySinkTime = BODY_SINK_DELAY; + max = BODY_QUEUE_SIZE; + } + + level.bodyQueIndex = 0; + for ( level.bodyQueSize = 0; + level.bodyQueSize < max && level.bodyQueSize < level.maxclients; + level.bodyQueSize++) + { + ent = G_Spawn(); + ent->classname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[level.bodyQueSize] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) +{ + if ( level.time - ent->timestamp > level.bodySinkTime + BODY_SINK_TIME ) + { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + + ent->s.eFlags |= EF_NOSHADOW; + + ent->nextthink = level.time + 100; + ent->s.pos.trBase[2] -= 1; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent, int hitLocation, vec3_t direction ) +{ + gentity_t *body; + int contents; + int parm; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->r.currentOrigin, -1 ); + if ( contents & CONTENTS_NODROP ) + { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % level.bodyQueSize; + + trap_UnlinkEntity (body); + + body->s = ent->s; + body->s.eType = ET_BODY; + body->s.eFlags = EF_DEAD; + body->s.gametypeitems = 0; + body->s.loopSound = 0; + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; + body->s.otherEntityNum = ent->s.clientNum; + + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) + { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } + else + { + body->s.pos.trType = TR_STATIONARY; + } + + body->s.event = 0; + + parm = (DirToByte( direction )&0xFF); + parm += (hitLocation<<8); + G_AddEvent(body, EV_BODY_QUEUE_COPY, parm); + + body->r.svFlags = ent->r.svFlags | SVF_BROADCAST; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->s.torsoAnim = body->s.legsAnim = ent->client->ps.legsAnim & ~ANIM_TOGGLEBIT; + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = 0; // CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + if ( level.bodySinkTime ) + { + body->nextthink = level.time + level.bodySinkTime; + body->think = BodySink; + body->s.time2 = 0; + } + else + { + // Store the time the body was spawned so the client can make them + // dissapear if need be. + body->s.time2 = level.time; + } + + body->die = body_die; + body->takedamage = qtrue; + + body->s.apos.trBase[PITCH] = 0; + + body->s.pos.trBase[2] = ent->client->ps.origin[2]; + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) +{ + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) + { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +G_SetRespawnTimer +================ +*/ +void G_SetRespawnTimer ( gentity_t* ent ) +{ + // Start a new respawn interval if the old one has passed + if ( level.time > level.gametypeRespawnTime[ent->client->sess.team] ) + { + level.gametypeRespawnTime[ent->client->sess.team] = level.time + g_respawnInterval.integer * 1000; + } + + // start the interval if its not already started + ent->client->ps.respawnTimer = level.gametypeRespawnTime[ent->client->sess.team] + 1000; +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) +{ + gentity_t *tent; + qboolean ghost = qfalse; + + // No respawning when intermission is queued + if ( level.intermissionQueued ) + { + return; + } + + // When we get here the user has just accepted their fate and now + // needs to wait for the ability to respawn + switch ( level.gametypeData->respawnType ) + { + case RT_INTERVAL: + G_SetRespawnTimer ( ent ); + ghost = qtrue; + break; + + case RT_NONE: + + // Turn into a ghost + ghost = qtrue; + break; + } + + // If they are a ghost then give a health point, but dont respawn + if ( ghost ) + { + G_StartGhosting ( ent ); + return; + } + + trap_UnlinkEntity (ent); + ClientSpawn(ent); + + // Add a teleportation effect. + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; +} + +/* +================ +G_GhostCount + +Returns number of ghosts on a team, if -1 is given for a team all ghosts in the game +are returned instead +================ +*/ +int G_GhostCount ( team_t team ) +{ + int i; + int count; + + for ( i = 0, count = 0; i < level.numConnectedClients; i ++ ) + { + if (g_entities[level.sortedClients[i]].client->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( g_entities[level.sortedClients[i]].client->sess.ghost ) + { + if ( team != -1 && team != g_entities[level.sortedClients[i]].client->sess.ghost ) + { + continue; + } + + count ++; + } + } + + return count; +} + +/* +================ +G_IsClientDead + +Returns qtrue if the client is dead and qfalse if not +================ +*/ +qboolean G_IsClientDead ( gclient_t* client ) +{ + if ( client->ps.stats[STAT_HEALTH] <= 0 ) + { + return qtrue; + } + + if ( client->ps.pm_type == PM_DEAD ) + { + return qtrue; + } + + if ( client->sess.ghost ) + { + return qtrue; + } + + return qfalse; +} + +/* +================ +G_IsClientSpectating + +Returns qtrue if the client is spectating and qfalse if not +================ +*/ +qboolean G_IsClientSpectating ( gclient_t* client ) +{ + if ( client->pers.connected != CON_CONNECTED ) + { + return qtrue; + } + + if ( client->sess.team == TEAM_SPECTATOR ) + { + return qtrue; + } + + if ( client->sess.ghost ) + { + return qtrue; + } + + return qfalse; +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +int TeamCount( int ignoreClientNum, team_t team, int *alive ) +{ + int i; + int count = 0; + + if ( alive ) + { + *alive = 0; + } + + for ( i = 0 ; i < level.maxclients ; i++ ) + { + if ( i == ignoreClientNum ) + { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) + { + continue; + } + + if ( level.clients[i].sess.team == team ) + { + if ( !level.clients[i].sess.ghost && alive ) + { + (*alive)++; + } + + count++; + } + } + + return count; +} + +/* +================ +PickTeam +================ +*/ +team_t PickTeam( int ignoreClientNum ) +{ + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE, NULL ); + counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED, NULL ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) + { + return TEAM_RED; + } + + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) + { + return TEAM_BLUE; + } + + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) + { + return TEAM_RED; + } + + return TEAM_BLUE; +} + +/* +=========== +G_ClientCleanName +============ +*/ +void G_ClientCleanName ( const char *in, char *out, int outSize, qboolean colors ) +{ + int len; + int colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) + { + ch = *in++; + if( !ch ) + { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) + { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) + { + // solo trailing carat is not a color prefix + if( !*in ) + { + break; + } + + // don't allow black in a name, period + if( !colors || ColorIndex(*in) == 0 ) + { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) + { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) + { + spaces++; + if( spaces > 3 ) + { + continue; + } + } + else + { + spaces = 0; + } + + if( len > outSize - 1 ) + { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + + *out = 0; + + // Trim whitespace off the end of the name + for ( out --; out >= p && (*out == ' ' || *out == '\t'); out -- ) + { + *out = 0; + } + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) + { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + +/* +=========== +Updates the clients current outfittin +=========== +*/ +void G_UpdateOutfitting ( int clientNum ) +{ + gentity_t *ent; + gclient_t *client; + int group; + int ammoIndex; + int idle; + + int equipWeapon; + int equipWeaponGroup; + + ent = g_entities + clientNum; + client = ent->client; + + // No can do if + if ( client->noOutfittingChange ) + { + return; + } + + // Clear all ammo, clips, and weapons + client->ps.stats[STAT_WEAPONS] = 0; + memset ( client->ps.ammo, 0, sizeof(client->ps.ammo) ); + memset ( client->ps.clip, 0, sizeof(client->ps.clip) ); + + // Everyone gets some knives + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_KNIFE ); + ammoIndex=weaponData[WP_KNIFE].attack[ATTACK_NORMAL].ammoIndex; + client->ps.clip[ATTACK_NORMAL][WP_KNIFE]=weaponData[WP_KNIFE].attack[ATTACK_NORMAL].clipSize; + client->ps.firemode[WP_KNIFE] = BG_FindFireMode ( WP_KNIFE, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + + if ( BG_IsWeaponAvailableForOutfitting ( WP_KNIFE, 2 ) ) + { + client->ps.ammo[ammoIndex]=ammoData[ammoIndex].max; + } + + equipWeapon = WP_KNIFE; + equipWeaponGroup = OUTFITTING_GROUP_KNIFE; + + // Give all the outfitting groups to the player + for ( group = 0; group < OUTFITTING_GROUP_ACCESSORY; group ++ ) + { + gitem_t* item; + int ammoIndex; + + // Nothing available in this group + if ( client->pers.outfitting.items[group] == -1 ) + { + continue; + } + + // Grab the item that represents the weapon + item = &bg_itemlist[bg_outfittingGroups[group][client->pers.outfitting.items[group]]]; + + client->ps.stats[STAT_WEAPONS] |= (1 << item->giTag); + ammoIndex = weaponData[item->giTag].attack[ATTACK_NORMAL].ammoIndex; + client->ps.ammo[ammoIndex] += weaponData[item->giTag].attack[ATTACK_NORMAL].extraClips * weaponData[item->giTag].attack[ATTACK_NORMAL].clipSize; + client->ps.clip[ATTACK_NORMAL][item->giTag] = weaponData[item->giTag].attack[ATTACK_NORMAL].clipSize; + + // Lower group numbers are bigger guns + if ( group < equipWeaponGroup ) + { + equipWeaponGroup = group; + equipWeapon = item->giTag; + } + + // alt-fire ammo + ammoIndex = weaponData[item->giTag].attack[ATTACK_ALTERNATE].ammoIndex; + if ( weaponData[item->giTag].attack[ATTACK_ALTERNATE].fireAmount && AMMO_NONE != ammoIndex ) + { + client->ps.clip[ATTACK_ALTERNATE][item->giTag] = weaponData[item->giTag].attack[ATTACK_ALTERNATE].clipSize; + client->ps.ammo[ammoIndex] += weaponData[item->giTag].attack[ATTACK_ALTERNATE].extraClips * weaponData[item->giTag].attack[ATTACK_ALTERNATE].clipSize; + } + + // Set the default firemode for this weapon + if ( client->ps.firemode[item->giTag] == WP_FIREMODE_NONE ) + { + client->ps.firemode[item->giTag] = BG_FindFireMode ( item->giTag, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + } + } + + client->ps.weapon = equipWeapon; + client->ps.weaponstate = WEAPON_READY; //WEAPON_SPAWNING; + client->ps.weaponTime = 0; + client->ps.weaponAnimTime = 0; + + // Bot clients cant use the spawning state +#ifdef _SOF2_BOTS + if ( ent->r.svFlags & SVF_BOT ) + { + client->ps.weaponstate = WEAPON_READY; + } +#endif + + // Default to auto (or next available fire mode). + BG_GetInviewAnim(client->ps.weapon,"idle",&idle); + client->ps.weaponAnimId = idle; + client->ps.weaponAnimIdChoice = 0; + client->ps.weaponCallbackStep = 0; + + // Armor? + client->ps.stats[STAT_ARMOR] = 0; + client->ps.stats[STAT_GOGGLES] = GOGGLES_NONE; + switch ( bg_outfittingGroups[OUTFITTING_GROUP_ACCESSORY][client->pers.outfitting.items[OUTFITTING_GROUP_ACCESSORY]] ) + { + default: + case MODELINDEX_ARMOR: + client->ps.stats[STAT_ARMOR] = MAX_HEALTH; + break; + + case MODELINDEX_THERMAL: + client->ps.stats[STAT_GOGGLES] = GOGGLES_INFRARED; + break; + + case MODELINDEX_NIGHTVISION: + client->ps.stats[STAT_GOGGLES] = GOGGLES_NIGHTVISION; + break; + } + + // Stuff which grenade is being used into stats for later use by + // the backpack code + client->ps.stats[STAT_OUTFIT_GRENADE] = bg_itemlist[bg_outfittingGroups[OUTFITTING_GROUP_GRENADE][client->pers.outfitting.items[OUTFITTING_GROUP_GRENADE]]].giTag; +} + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) +{ + gentity_t *ent; + int team; + int health; + char *s; + gclient_t *client; + char oldname[MAX_STRING_CHARS]; + char userinfo[MAX_INFO_STRING]; + TIdentity *oldidentity; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) + { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( s, "localhost" ) ) + { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) + { + client->pers.predictItemPickup = qfalse; + } + else + { + client->pers.predictItemPickup = qtrue; + } + + // Is anti-lag turned on? + s = Info_ValueForKey ( userinfo, "cg_antiLag" ); + client->pers.antiLag = atoi( s )?qtrue:qfalse; + + // Is auto-reload turned on? + s = Info_ValueForKey ( userinfo, "cg_autoReload" ); + client->pers.autoReload = atoi( s )?qtrue:qfalse; + if ( client->pers.autoReload ) + { + client->ps.pm_flags |= PMF_AUTORELOAD; + } + else + { + client->ps.pm_flags &= ~PMF_AUTORELOAD; + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + G_ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname), level.gametypeData->teams?qfalse:qtrue ); + + if ( client->sess.team == TEAM_SPECTATOR ) + { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); + } + } + + // set max health + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + + // bots set their team a few frames later + if ( level.gametypeData->teams && (g_entities[clientNum].r.svFlags & SVF_BOT)) + { + s = Info_ValueForKey( userinfo, "team" ); + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) + { + team = TEAM_RED; + } + else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) + { + team = TEAM_BLUE; + } + else + { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + } + else + { + team = client->sess.team; + } + + // Enforce the identities + oldidentity = client->pers.identity; + + if( level.gametypeData->teams ) + { + s = Info_ValueForKey ( userinfo, "team_identity" ); + + // Lookup the identity by name and if it cant be found then pick a random one + client->pers.identity = BG_FindIdentity ( s ); + + if ( team != TEAM_SPECTATOR ) + { + // No identity or a team mismatch means they dont get to be that skin + if ( !client->pers.identity || Q_stricmp ( level.gametypeTeam[team], client->pers.identity->mTeam ) ) + { + // Get first matching team identity + client->pers.identity = BG_FindTeamIdentity ( level.gametypeTeam[team], -1 ); + } + } + else + { + // Spectators are going to have to choose one of the two team skins and + // the chance of them having the proper one in team_identity is slim, so just + // give them a model they may use later + client->pers.identity = BG_FindTeamIdentity ( level.gametypeTeam[TEAM_RED], 0 ); + } + } + else + { + s = Info_ValueForKey ( userinfo, "identity" ); + + // Lookup the identity by name and if it cant be found then pick a random one + client->pers.identity = BG_FindIdentity ( s ); + } + + // If the identity wasnt in the list then just give them the first identity. We could + // be fancy here and give them a random one, but this way you get less unwanted models + // loaded + if ( !client->pers.identity ) + { + client->pers.identity = &bg_identities[0]; + } + + // Report the identity change + if ( client->pers.connected == CON_CONNECTED ) + { + if ( client->pers.identity && oldidentity && client->pers.identity != oldidentity && team != TEAM_SPECTATOR ) + { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " has changed identities\n\"", client->pers.netname ) ); + } + + // If the client is changing their name then handle some delayed name changes + if ( strcmp( oldname, client->pers.netname ) ) + { + // Dont let them change their name too much + if ( level.time - client->pers.netnameTime < 5000 ) + { + trap_SendServerCommand ( client - &level.clients[0], "print \"You must wait 5 seconds before changing your name again.\n\"" ); + strcpy ( client->pers.netname, oldname ); + } + // voting clients cannot change their names + else if ( (level.voteTime || level.voteExecuteTime) && strstr ( level.voteDisplayString, oldname ) ) + { + trap_SendServerCommand ( client - &level.clients[0], "print \"You are not allowed to change your name while there is an active vote against you.\n\"" ); + strcpy ( client->pers.netname, oldname ); + } + // If they are a ghost or spectating in an inf game their name is deferred + else if ( level.gametypeData->respawnType == RT_NONE && (client->sess.ghost || G_IsClientDead ( client ) ) ) + { + trap_SendServerCommand ( client - &level.clients[0], "print \"Name changes while dead will be deferred until you spawn again.\n\"" ); + strcpy ( client->pers.deferredname, client->pers.netname ); + strcpy ( client->pers.netname, oldname ); + } + else + { + trap_SendServerCommand( -1, va("print \"%s renamed to %s\n\"", oldname, client->pers.netname) ); + client->pers.netnameTime = level.time; + } + } + } + + // Outfitting if pickups are disabled + if ( level.pickupsDisabled ) + { + // Parse out the new outfitting + BG_DecompressOutfitting ( Info_ValueForKey ( userinfo, "outfitting" ), &client->pers.outfitting ); + G_UpdateOutfitting ( clientNum ); + } + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) + { + s = va("n\\%s\\t\\%i\\identity\\%s\\skill\\%s", + client->pers.netname, team, client->pers.identity->mName, + Info_ValueForKey( userinfo, "skill" ) ); + } + else + { + s = va("n\\%s\\t\\%i\\identity\\%s", + client->pers.netname, team, client->pers.identity->mName ); + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or map restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and map +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) +{ + char *value; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) + { + return "Banned."; + } + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !( isBot ) && (strcmp(value, "localhost") != 0)) + { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value) != 0) + { + return va("Invalid password: %s", value ); + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + client->sess.team = TEAM_SPECTATOR; + + // read or initialize the session data + if ( firstTime || level.newSession ) + { + G_InitSessionData( client, userinfo ); + } + + G_ReadSessionData( client ); + + if( isBot ) + { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) + { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) + { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " is connecting...\n\"", client->pers.netname) ); + } + + // Broadcast team change if not going to spectator + if ( level.gametypeData->teams && client->sess.team != TEAM_SPECTATOR ) + { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + // Make sure they are unlinked + trap_UnlinkEntity ( ent ); + + return NULL; +} + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) +{ + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags; + int spawncount; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + if ( ent->r.linked ) + { + trap_UnlinkEntity( ent ); + } + + // Run a gametype check just in case the game hasnt started yet + if ( !level.gametypeStartTime ) + { + CheckGametype ( ); + } + + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + spawncount = client->ps.persistant[PERS_SPAWN_COUNT]; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + client->ps.persistant[PERS_SPAWN_COUNT] = spawncount; + + // locate ent at a spawn point + ClientSpawn( ent ); + + if ( client->sess.team != TEAM_SPECTATOR ) + { + // send event + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); + } + + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); +} + +/* +=========== +G_SelectClientSpawnPoint + +Selects a spawn point for the given client entity +============ +*/ +gspawn_t* G_SelectClientSpawnPoint ( gentity_t* ent ) +{ + gclient_t* client = ent->client; + gspawn_t* spawnPoint; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if ( client->sess.team == TEAM_SPECTATOR ) + { + spawnPoint = G_SelectSpectatorSpawnPoint ( ); + } + else + { + if ( level.gametypeData->teams && level.gametypeData->respawnType != RT_NORMAL ) + { + // Dont bother selecting a safe spawn on non-respawn games, the map creator should + // have done this for us. + if ( level.gametypeData->respawnType == RT_NONE ) + { + spawnPoint = G_SelectRandomSpawnPoint ( ent->client->sess.team ); + } + else + { + spawnPoint = G_SelectRandomSafeSpawnPoint ( ent->client->sess.team, 1500 ); + } + + if ( !spawnPoint ) + { + // don't spawn near other players if possible + spawnPoint = G_SelectRandomSpawnPoint ( ent->client->sess.team ); + } + + // Spawn at any deathmatch spawn, telefrag if needed + if ( !spawnPoint ) + { + spawnPoint = G_SelectRandomSpawnPoint ( TEAM_FREE ); + } + } + else + { + // Try deathmatch spawns first + spawnPoint = G_SelectRandomSafeSpawnPoint ( TEAM_FREE, 1500 ); + + // If none found use any spawn + if ( !spawnPoint ) + { + spawnPoint = G_SelectRandomSafeSpawnPoint ( -1, 1500 ); + } + + // Spawn at any deathmatch spawn, telefrag if needed + if ( !spawnPoint ) + { + spawnPoint = G_SelectRandomSpawnPoint ( TEAM_FREE ); + } + + // Spawn at any gametype spawn, telefrag if needed + if ( !spawnPoint ) + { + spawnPoint = G_SelectRandomSpawnPoint ( -1 ); + } + } + } + + return spawnPoint; +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ + +void ClientSpawn(gentity_t *ent) +{ + int index; + vec3_t spawn_origin; + vec3_t spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gspawn_t *spawnPoint; + int flags; + int savedPing; + int eventSequence; + char userinfo[MAX_INFO_STRING]; + int start_ammo_type; + int ammoIndex; + int idle; + + index = ent - g_entities; + client = ent->client; + + // Where do we spawn? + spawnPoint = G_SelectClientSpawnPoint ( ent ); + if ( spawnPoint ) + { + VectorCopy ( spawnPoint->angles, spawn_angles ); + VectorCopy ( spawnPoint->origin, spawn_origin ); + spawn_origin[2] += 9; + } + else + { + SetTeam ( ent, "s", NULL ); + return; + } + + client->pers.teamState.state = TEAM_ACTIVE; + + // toggle the teleport bit so the client knows to not lerp + // and never clear the voted flag + flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED); + flags ^= EF_TELEPORT_BIT; + + // clear everything but the persistant data + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) + { + persistant[i] = client->ps.persistant[i]; + } + eventSequence = client->ps.eventSequence; + + memset (client, 0, sizeof(*client)); + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + client->lastkilled_client = -1; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) + { + client->ps.persistant[i] = persistant[i]; + } + client->ps.eventSequence = eventSequence; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.team; + client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + + // clear entity values + client->ps.eFlags = flags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + + client->ps.clientNum = index; + + // Bring back the saved firemodes + memcpy ( client->ps.firemode, client->pers.firemode, sizeof(client->ps.firemode) ); + + //give default weapons + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); + + client->noOutfittingChange = qfalse; + + // Give the client their weapons depending on whether or not pickups are enabled + if ( level.pickupsDisabled ) + { + G_UpdateOutfitting ( ent->s.number ); + + // Prevent the client from picking up a whole bunch of stuff + client->ps.pm_flags |= PMF_LIMITED_INVENTORY; + } + else + { + // Knife. + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_KNIFE ); + ammoIndex=weaponData[WP_KNIFE].attack[ATTACK_NORMAL].ammoIndex; + client->ps.ammo[ammoIndex]=ammoData[ammoIndex].max; + client->ps.clip[ATTACK_NORMAL][WP_KNIFE]=weaponData[WP_KNIFE].attack[ATTACK_NORMAL].clipSize; + client->ps.firemode[WP_KNIFE] = BG_FindFireMode ( WP_KNIFE, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + + // Set up some weapons and ammo for the player. + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_USSOCOM_PISTOL ); + start_ammo_type = weaponData[WP_USSOCOM_PISTOL].attack[ATTACK_NORMAL].ammoIndex; + client->ps.ammo[start_ammo_type] = weaponData[WP_USSOCOM_PISTOL].attack[ATTACK_NORMAL].clipSize; + client->ps.clip[ATTACK_NORMAL][WP_USSOCOM_PISTOL] = weaponData[WP_USSOCOM_PISTOL].attack[ATTACK_NORMAL].clipSize; + client->ps.firemode[WP_USSOCOM_PISTOL] = BG_FindFireMode ( WP_USSOCOM_PISTOL, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + + // alt-fire ammo + start_ammo_type = weaponData[WP_USSOCOM_PISTOL].attack[ATTACK_ALTERNATE].ammoIndex; + if (AMMO_NONE != start_ammo_type) + { + client->ps.ammo[start_ammo_type] = ammoData[start_ammo_type].max; + } + + // Everyone gets full armor in deathmatch + client->ps.stats[STAT_ARMOR] = MAX_HEALTH; + } + + client->ps.stats[STAT_HEALTH] = ent->health = MAX_HEALTH; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + if ( client->pers.autoReload ) + { + client->ps.pm_flags |= PMF_AUTORELOAD; + } + else + { + client->ps.pm_flags &= ~PMF_AUTORELOAD; + } + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.team != TEAM_SPECTATOR ) + { + G_KillBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + if ( !level.pickupsDisabled ) + { + client->ps.weapon = WP_USSOCOM_PISTOL; + client->ps.weaponstate = WEAPON_RAISING; + client->ps.weaponTime = 500; + + // Default to auto (or next available fire mode). + client->ps.firemode[client->ps.weapon] = BG_FindFireMode ( client->ps.weapon, ATTACK_NORMAL, WP_FIREMODE_AUTO ); + BG_GetInviewAnim(client->ps.weapon,"idle",&idle); + client->ps.weaponAnimId = idle; + client->ps.weaponAnimIdChoice = 0; + client->ps.weaponCallbackStep = 0; + } + } + else + { + client->ps.weapon = WP_KNIFE; + BG_GetInviewAnim(client->ps.weapon,"idle",&idle); + client->ps.weaponAnimId = idle; + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->invulnerableTime = level.time; + client->ps.eFlags |= EF_INVULNERABLE; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.weaponstate = WEAPON_READY; + client->ps.torsoAnim = -1; + client->ps.legsAnim = LEGS_IDLE; + + // Not on a ladder + client->ps.ladder = -1; + + // Not leaning + client->ps.leanTime = LEAN_TIME; + + if ( level.intermissiontime ) + { + MoveClientToIntermission( ent ); + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.team != TEAM_SPECTATOR ) + { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + // Frozen? + if ( level.gametypeDelayTime > level.time ) + { + ent->client->ps.stats[STAT_FROZEN] = level.gametypeDelayTime - level.time; + } + + // Handle a deferred name change + if ( client->pers.deferredname[0] ) + { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", client->pers.netname, client->pers.deferredname) ); + strcpy ( client->pers.netname, client->pers.deferredname ); + client->pers.deferredname[0] = '\0'; + client->pers.netnameTime = level.time; + ClientUserinfoChanged ( client->ps.clientNum ); + } + + // Update the time when other people can join the game + if ( !level.gametypeJoinTime && level.gametypeData->teams ) + { + // As soon as both teams have people on them the counter starts + if ( TeamCount ( -1, TEAM_RED, NULL ) && TeamCount ( -1, TEAM_BLUE, NULL ) ) + { + level.gametypeJoinTime = level.time; + } + } +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) +{ + gentity_t *ent; + gentity_t *tent; + int i; + + // cleanup if we are kicking a bot that + // hasn't spawned yet + G_RemoveQueuedBotBegin( clientNum ); + + ent = g_entities + clientNum; + if ( !ent->client ) + { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) + { + if ( G_IsClientSpectating ( &level.clients[i] ) && + level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[i].sess.spectatorClient == clientNum ) + { + G_StopFollowing( &g_entities[i] ); + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED && + !G_IsClientSpectating ( ent->client ) && + !G_IsClientDead ( ent->client ) ) + { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // Dont drop weapons + ent->client->ps.stats[STAT_WEAPONS] = 0; + + // Get rid of things that need to drop + TossClientItems( ent ); + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + trap_UnlinkEntity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.team = TEAM_FREE; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + +#ifdef _SOF2_BOTS + if ( ent->r.svFlags & SVF_BOT ) + { + BotAIShutdownClient( clientNum, qfalse ); + } +#endif +} + +/* +=========== +G_UpdateClientAnimations + +Updates the animation information for the client +============ +*/ +void G_UpdateClientAnimations ( gentity_t* ent ) +{ + gclient_t* client; + + client = ent->client; + + // Check for anim change in the legs + if ( client->legs.anim != ent->s.legsAnim ) + { + client->legs.anim = ent->s.legsAnim; + client->legs.animTime = level.time; + } + + // Check for anim change in the torso + if ( client->torso.anim != ent->s.torsoAnim ) + { + client->torso.anim = ent->s.torsoAnim; + client->torso.animTime = level.time; + } + + // Force the legs and torso to stay aligned for now to ensure the client + // and server are in sync with the angles. + // TODO: Come up with a way to keep these in sync on both client and server + client->torso.yawing = qtrue; + client->torso.pitching = qtrue; + client->legs.yawing = qtrue; + + // Calculate the real torso and leg angles + BG_PlayerAngles ( ent->client->ps.viewangles, + NULL, + + ent->client->ghoulLegsAngles, + ent->client->ghoulLowerTorsoAngles, + ent->client->ghoulUpperTorsoAngles, + ent->client->ghoulHeadAngles, + + (float)(ent->client->ps.leanTime - LEAN_TIME) / LEAN_TIME * LEAN_OFFSET, + + 0, + 0, + level.time, + + &client->torso, + &client->legs, + + level.time - level.previousTime, + + client->ps.velocity, + + qfalse, + ent->s.angles2[YAW], + NULL ); +} + +/* +=========== +G_FindNearbyClient + +Locates a client thats near the given origin +============ +*/ +gentity_t* G_FindNearbyClient ( vec3_t origin, team_t team, float radius, gentity_t* ignore ) +{ + int i; + + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t* other = &g_entities[ level.sortedClients[i] ]; + vec3_t diff; + + if ( other->client->pers.connected != CON_CONNECTED ) + { + continue; + } + + if ( other == ignore ) + { + continue; + } + + if ( G_IsClientSpectating ( other->client ) || G_IsClientDead ( other->client ) ) + { + continue; + } + + if ( other->client->sess.team != team ) + { + continue; + } + + // See if this client is close enough to yell sniper + VectorSubtract ( other->r.currentOrigin, origin, diff ); + if ( VectorLengthSquared ( diff ) < radius * radius ) + { + return other; + } + } + + return NULL; +} diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c new file mode 100644 index 0000000..c78d1c9 --- /dev/null +++ b/code/game/g_cmds.c @@ -0,0 +1,1981 @@ +// Copyright (C) 2001-2002 Raven Software. +// +#include "g_local.h" + +#include "../../ui/menudef.h" + +int AcceptBotCommand(char *cmd, gentity_t *pl); + +/* +================== +DeathmatchScoreboardMessage +================== +*/ +void DeathmatchScoreboardMessage( gentity_t *ent ) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + + numSorted = level.numConnectedClients; + + for (i=0 ; i < numSorted ; i++) + { + int ping; + + cl = &level.clients[level.sortedClients[i]]; + + if ( cl->pers.connected == CON_CONNECTING ) + { + ping = -1; + } + else + { + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + } + + Com_sprintf (entry, sizeof(entry), + " %i %i %i %i %i %i %i %i %i", + level.sortedClients[i], + cl->sess.score, + cl->sess.kills, + cl->sess.deaths, + ping, + (level.time - cl->pers.enterTime)/60000, + (cl->sess.ghost || cl->ps.pm_type == PM_DEAD) ? qtrue : qfalse, + g_entities[level.sortedClients[i]].s.gametypeitems, + g_teamkillDamageMax.integer ? 100 * cl->sess.teamkillDamage / g_teamkillDamageMax.integer : 0 + ); + + j = strlen(entry); + if (stringlength + j > 1022 ) + { + break; + } + + strcpy (string + stringlength, entry); + stringlength += j; + } + + trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, + level.teamScores[TEAM_RED], + level.teamScores[TEAM_BLUE], + string ) ); +} + + +/* +================== +Cmd_Score_f + +Request current scoreboard information +================== +*/ +void Cmd_Score_f( gentity_t *ent ) +{ + DeathmatchScoreboardMessage( ent ); +} + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) { + if ( !g_cheats.integer ) { + trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); + return qfalse; + } + if ( ent->health <= 0 ) { + trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\"")); + return qfalse; + } + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) { + int i, c, tlen; + static char line[MAX_STRING_CHARS]; + int len; + char arg[MAX_STRING_CHARS]; + + len = 0; + c = trap_Argc(); + for ( i = start ; i < c ; i++ ) { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + if ( len + tlen >= MAX_STRING_CHARS - 1 ) { + break; + } + memcpy( line + len, arg, tlen ); + len += tlen; + if ( i != c - 1 ) { + line[len] = ' '; + len++; + } + } + + line[len] = 0; + + return line; +} + +/* +================== +SanitizeString + +Remove case and control characters +================== +*/ +void SanitizeString( char *in, char *out ) { + while ( *in ) { + if ( *in == 27 ) { + in += 2; // skip color code + continue; + } + if ( *in < 32 ) { + in++; + continue; + } + *out++ = tolower( *in++ ); + } + + *out = 0; +} + +/* +================== +G_ClientNumberFromName + +Finds the client number of the client with the given name +================== +*/ +int G_ClientNumberFromName ( const char* name ) +{ + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + int i; + gclient_t* cl; + + // check for a name match + SanitizeString( (char*)name, s2 ); + for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) + { + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) + { + return i; + } + } + + return -1; +} + +/* +================== +ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int ClientNumberFromString( gentity_t *to, char *s ) { + gclient_t *cl; + int idnum; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + + // numeric values are just slot numbers + if (s[0] >= '0' && s[0] <= '9') { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); + return -1; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected != CON_CONNECTED ) { + trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); + return -1; + } + return idnum; + } + + // check for a name match + SanitizeString( s, s2 ); + for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) { + return idnum; + } + } + + trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); + return -1; +} + +/* +================== +Cmd_Drop_f + +Drops the currenty selected weapon +================== +*/ +void Cmd_Drop_f ( gentity_t* ent ) +{ + gentity_t* dropped; + + // spectators cant drop anything since they dont have anything + if ( ent->client->sess.team == TEAM_SPECTATOR ) + { + return; + } + + // Ghosts and followers cant drop stuff + if ( ent->client->ps.pm_flags & (PMF_GHOST|PMF_FOLLOW) ) + { + return; + } + + // Drop the weapon the client wanted to drop + dropped = G_DropWeapon ( ent, atoi(ConcatArgs( 1 )), 3000 ); + if ( !dropped ) + { + return; + } +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (gentity_t *ent) +{ + char *name; + gitem_t *it; + int i; + qboolean give_all; + gentity_t *it_ent; + trace_t trace; + char arg[MAX_QPATH]; + + int start; + int end; + int l; + + trap_Argv( 1, arg, sizeof( arg ) ); + + if ( !Q_stricmp ( arg, "me" ) ) + { + start = ent->s.number; + end = start + 1; + } + else if ( !Q_stricmp ( arg, "all" ) ) + { + start = 0; + end = MAX_CLIENTS; + } + else + { + start = atoi ( arg ); + end = start + 1; + } + + for ( l = start; l < end; l ++ ) + { + ent = &g_entities[l]; + + if ( !ent->inuse ) + { + continue; + } + + if ( G_IsClientDead ( ent->client ) ) + { + continue; + } + + if ( !CheatsOk( ent ) ) { + return; + } + + name = ConcatArgs( 2 ); + + if (Q_stricmp(name, "all") == 0) + give_all = qtrue; + else + give_all = qfalse; + + if (give_all || Q_stricmp( name, "health") == 0) + { + ent->health = MAX_HEALTH; + if (!give_all) + continue; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - ( 1 << WP_NONE ); + if (!give_all) + continue; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for ( i = WP_NONE + 1 ; i < WP_NUM_WEAPONS ; i++ ) + { + attackType_t a; + + for ( a = ATTACK_NORMAL; a < ATTACK_MAX; a ++ ) + { + ent->client->ps.clip[a][i] = weaponData[i].attack[a].clipSize; + ent->client->ps.ammo[weaponData[i].attack[a].ammoIndex] = ammoData[weaponData[i].attack[a].ammoIndex].max; + } + } + + if (!give_all) + continue; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + ent->client->ps.stats[STAT_ARMOR] = MAX_ARMOR; + + if (!give_all) + continue; + } + + // spawn a specific item right on the player + if ( !give_all ) + { + it = BG_FindItem (name); + if (!it) + { + continue; + } + + if ( it->giType == IT_GAMETYPE ) + { + continue; + } + + it_ent = G_Spawn(); + VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); + it_ent->classname = it->classname; + G_SpawnItem (it_ent, it); + FinishSpawningItem(it_ent ); + memset( &trace, 0, sizeof( trace ) ); + Touch_Item (it_ent, ent, &trace); + if (it_ent->inuse) + { + G_FreeEntity( it_ent ); + } + } + + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (gentity_t *ent) +{ + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) +{ + if ( !CheatsOk( ent ) ) + { + return; + } + + BeginIntermission(); + + trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) +{ + // No killing yourself if your a spectator + if ( G_IsClientSpectating ( ent->client ) ) + { + return; + } + + // No killing yourself if your dead + if ( G_IsClientDead ( ent->client ) ) + { + return; + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_SUICIDE, HL_NONE, vec3_origin ); +} + +/* +================= +BroadCastTeamChange + +Let everyone know about a team change +================= +*/ +void BroadcastTeamChange( gclient_t *client, int oldTeam ) +{ + switch ( client->sess.team ) + { + case TEAM_RED: + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", client->pers.netname) ); + break; + + case TEAM_BLUE: + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", client->pers.netname)); + break; + + case TEAM_SPECTATOR: + if ( oldTeam != TEAM_SPECTATOR ) + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", client->pers.netname)); + } + break; + + case TEAM_FREE: + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", client->pers.netname)); + break; + } +} + +/* +================= +SetTeam +================= +*/ +void SetTeam( gentity_t *ent, char *s, const char* identity ) +{ + int team; + int oldTeam; + gclient_t *client; + int clientNum; + spectatorState_t specState; + int specClient; + qboolean ghost; + qboolean noOutfittingChange = qfalse; + + // see what change is requested + // + client = ent->client; + + clientNum = client - level.clients; + specClient = 0; + specState = SPECTATOR_NOT; + + // If an identity was specified then inject it into + // the clients userinfo + if ( identity ) + { + char userinfo[MAX_INFO_STRING]; + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + if ( Q_stricmp ( identity, Info_ValueForKey ( userinfo, "identity" ) ) ) + { + Info_SetValueForKey ( userinfo, "identity", identity ); + Info_SetValueForKey ( userinfo, "team_identity", identity ); + trap_SetUserinfo ( clientNum, userinfo ); + } + else + { + identity = NULL; + } + } + + if ( !Q_stricmp( s, "follow1" ) ) + { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -1; + } + else if ( !Q_stricmp( s, "follow2" ) ) + { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -2; + } + else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) + { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FREE; + } + else if ( level.gametypeData->teams ) + { + // if running a team game, assign player to one of the teams + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) + { + team = TEAM_RED; + } + else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) + { + team = TEAM_BLUE; + } + else + { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + + if ( g_teamForceBalance.integer ) + { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE, NULL ); + counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED, NULL ); + + // We allow a spread of two + if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) + { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"Red team has too many players.\n\"" ); + + // ignore the request + return; + } + if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) + { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"Blue team has too many players.\n\"" ); + + // ignore the request + return; + } + + // It's ok, the team we are switching to has less or same number of players + } + } + else + { + // force them to spectators if there aren't any spots free + team = TEAM_FREE; + } + + // override decision if limiting the players + if ( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) + { + team = TEAM_SPECTATOR; + } + + // decide if we will allow the change + oldTeam = client->sess.team; + ghost = client->sess.ghost; + + if ( team == oldTeam && team != TEAM_SPECTATOR ) + { + if ( identity ) + { + // get and distribute relevent paramters + client->pers.identity = NULL; + ClientUserinfoChanged( clientNum ); + } + + return; + } + + noOutfittingChange = ent->client->noOutfittingChange; + + // he starts at 'base' + client->pers.teamState.state = TEAM_BEGIN; + + if ( oldTeam != TEAM_SPECTATOR ) + { + if ( ghost ) + { + G_StopGhosting ( ent ); + } + else if ( !G_IsClientDead ( client ) ) + { + // Kill him (makes sure he loses flags, etc) + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die (ent, ent, ent, 100000, MOD_TEAMCHANGE, HL_NONE, vec3_origin ); + + ent->client->sess.ghost = qfalse; + } + } + + // If respawn interval start as a ghost + if ( level.gametypeRespawnTime[ team ] ) + { + ghost = qtrue; + } + + // they go to the end of the line + if ( team == TEAM_SPECTATOR ) + { + client->sess.spectatorTime = level.time; + } + + client->sess.team = team; + client->sess.spectatorState = specState; + client->sess.spectatorClient = specClient; + + // Always spawn into a ctf game using a respawn timer. + if ( team != TEAM_SPECTATOR && level.gametypeData->respawnType == RT_INTERVAL ) + { + G_SetRespawnTimer ( ent ); + ghost = qtrue; + } + + BroadcastTeamChange( client, oldTeam ); + + // See if we should spawn as a ghost + if ( team != TEAM_SPECTATOR && level.gametypeData->respawnType == RT_NONE ) + { + // If there are ghosts already then spawn as a ghost because + // the game is already in progress. + if ( (level.gametypeJoinTime && (level.time - level.gametypeJoinTime) > 20000) || noOutfittingChange || client->sess.noTeamChange ) + { + ghost = qtrue; + } + + // Spectator to a team doesnt count + if ( oldTeam != TEAM_SPECTATOR ) + { + client->sess.noTeamChange = qtrue; + } + } + + // If a ghost, enforce it + if ( ghost ) + { + // Make them a ghost again + if ( team != TEAM_SPECTATOR ) + { + G_StartGhosting ( ent ); + + // get and distribute relevent paramters + client->pers.identity = NULL; + ClientUserinfoChanged( clientNum ); + + CalculateRanks(); + + return; + } + } + + // get and distribute relevent paramters + client->pers.identity = NULL; + ClientUserinfoChanged( clientNum ); + + CalculateRanks(); + + // Begin the clients new life on the their new team + ClientBegin( clientNum ); +} + +/* +================= +G_StartGhosting + +Starts a client ghosting. This essentially will kill a player which is alive +================= +*/ +void G_StartGhosting ( gentity_t* ent ) +{ + int i; + + // Dont start ghosting if already ghosting + if ( ent->client->sess.ghost ) + { + return; + } + + ent->client->sess.ghost = qtrue; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->sess.spectatorClient = -1; + ent->client->ps.pm_flags |= PMF_GHOST; + ent->client->ps.stats[STAT_HEALTH] = 100; + ent->client->ps.pm_type = PM_SPECTATOR; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + + trap_UnlinkEntity (ent); + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) + { + if ( G_IsClientSpectating ( &level.clients[i] ) + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == ent->s.number ) + { + G_StopFollowing( &g_entities[i] ); + } + } +} + +/* +================= +G_StopGhosting + +Stops a client from ghosting. The client will be dead after this +call +================= +*/ +void G_StopGhosting ( gentity_t* ent ) +{ + // Dont stop someone who isnt ghosting in the first place + if ( !ent->client->sess.ghost ) + { + return; + } + + ent->client->sess.ghost = qfalse; + ent->client->ps.pm_flags &= ~PMF_GHOST; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + + if ( ent->client->sess.team == TEAM_SPECTATOR ) + { + ent->client->ps.pm_type = PM_SPECTATOR; + } + else + { + ent->client->ps.pm_type = PM_DEAD; + ent->health = ent->client->ps.stats[STAT_HEALTH] = 0; + } +} + +/* +================= +G_StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode +================= +*/ +void G_StopFollowing( gentity_t *ent ) +{ + // Cant stop following if not following in the first place + if ( !(ent->client->ps.pm_flags&PMF_FOLLOW) ) + { + return; + } + + // Clear the following variables + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->ps.clientNum = ent - g_entities; + ent->client->ps.zoomFov = 0; + ent->client->ps.pm_flags &= ~(PMF_GOGGLES_ON|PMF_ZOOM_FLAGS); + ent->client->ps.persistant[PERS_TEAM] = ent->client->sess.team; + ent->r.svFlags &= ~SVF_BOT; + + // Ghots dont really become spectators, just psuedo spectators + if ( ent->client->sess.ghost ) + { + // Do a start and stop to ensure the variables are all set properly + G_StopGhosting ( ent ); + G_StartGhosting ( ent ); + } + else + { + ent->client->sess.team = TEAM_SPECTATOR; + ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; + } + + // If we were in fact following someone, then make the angles and origin nice for + // when we stop + if ( ent->client->sess.spectatorClient != -1 ) + { + gclient_t* cl = &level.clients[ent->client->sess.spectatorClient]; + + int i; + for ( i = 0; i < 3; i ++ ) + { + ent->client->ps.delta_angles[i] = ANGLE2SHORT(cl->ps.viewangles[i] - SHORT2ANGLE(ent->client->pers.cmd.angles[i])); + } + + VectorCopy ( cl->ps.viewangles, ent->client->ps.viewangles ); + VectorCopy ( cl->ps.origin, ent->client->ps.origin ); + VectorClear ( ent->client->ps.velocity ); + ent->client->ps.movementDir = 0; + + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + + ent->client->sess.spectatorClient = -1; +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) +{ + char team[MAX_TOKEN_CHARS]; + char identity[MAX_TOKEN_CHARS]; + + // Need at least the team specified in the arguments + if ( trap_Argc() < 2 ) + { + int oldTeam = ent->client->sess.team; + switch ( oldTeam ) + { + case TEAM_BLUE: + trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); + break; + + case TEAM_RED: + trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); + break; + + case TEAM_FREE: + trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); + break; + + case TEAM_SPECTATOR: + trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); + break; + } + + return; + } + + // Limit how often one can switch team + if ( ent->client->switchTeamTime > level.time ) + { + trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); + return; + } + + trap_Argv( 1, team, sizeof( team ) ); + trap_Argv( 2, identity, sizeof( identity ) ); + + SetTeam( ent, team, identity[0]?identity:NULL ); + + // Remember the team switch time so they cant do it again really quick + ent->client->switchTeamTime = level.time + 5000; +} + + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) +{ + int i; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) + { + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + { + G_StopFollowing( ent ); + } + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + i = ClientNumberFromString( ent, arg ); + if ( i == -1 ) + { + return; + } + + // can't follow self + if ( &level.clients[ i ] == ent->client ) + { + return; + } + + // cant cycle to dead people + if ( level.clients[i].ps.pm_type == PM_DEAD ) + { + return; + } + + // can't follow another spectator + if ( G_IsClientSpectating ( &level.clients[ i ] ) ) + { + return; + } + + // first set them to spectator as long as they arent a ghost + if ( !ent->client->sess.ghost && ent->client->sess.team != TEAM_SPECTATOR ) + { + SetTeam( ent, "spectator", NULL ); + } + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent, int dir ) +{ + int clientnum; + int deadclient; + int original; + + // first set them to spectator + if ( !ent->client->sess.ghost && ent->client->sess.team != TEAM_SPECTATOR ) + { + SetTeam( ent, "spectator", NULL ); + } + + if ( dir != 1 && dir != -1 ) + { + Com_Error( ERR_FATAL, "Cmd_FollowCycle_f: bad dir %i", dir ); + } + + if ( ent->client->sess.spectatorClient == -1 ) + { + clientnum = original = ent->s.number; + } + else + { + clientnum = original = ent->client->sess.spectatorClient; + } + + deadclient = -1; + do + { + clientnum += dir; + if ( clientnum >= level.maxclients ) + { + clientnum = 0; + } + if ( clientnum < 0 ) + { + clientnum = level.maxclients - 1; + } + + // can only follow connected clients + if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) + { + continue; + } + + // can't follow another spectator + if ( G_IsClientSpectating ( &level.clients[ clientnum ] ) ) + { + continue; + } + + // Cant switch to dead people unless there is nobody else to switch to + if ( G_IsClientDead ( &level.clients[clientnum] ) ) + { + deadclient = clientnum; + continue; + } + + // Dissallow following of the enemy if the cvar is set + if ( level.gametypeData->teams && !g_followEnemy.integer && ent->client->sess.team != TEAM_SPECTATOR ) + { + // Are they on the same team? + if ( level.clients[ clientnum ].sess.team != ent->client->sess.team ) + { + continue; + } + } + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + return; + + } while ( clientnum != original ); + + // If being forced to follow and there is a dead client to jump to, then jump to them now + if ( deadclient != -1 && g_forceFollow.integer ) + { + // this is good, we can use it + ent->client->sess.spectatorClient = deadclient; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + return; + } + + G_StopFollowing( ent ); + + // leave it where it was +} + +/* +================== +G_SayTo +================== +*/ +static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, const char *name, const char *message ) +{ + qboolean ghost = qfalse; + qboolean spec = qfalse; + const char* type; + + if (!other) + { + return; + } + + if (!other->inuse) + { + return; + } + + if (!other->client) + { + return; + } + + if ( other->client->pers.connected != CON_CONNECTED ) + { + return; + } + + if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) + { + return; + } + + if ( !level.intermissiontime && !level.intermissionQueued ) + { + // Spectators cant talk to alive people + if ( ent->client->sess.team == TEAM_SPECTATOR ) + { + spec = qtrue; + } + + if ( level.gametypeData->respawnType == RT_NONE ) + { + // Dead people cant talk to alive people + if ( !spec && G_IsClientDead ( ent->client ) ) + { + ghost = qtrue; + } + + // If the client we are talking to is alive then a check + // must be made to see if this talker is alowed to speak to this person + if ( ent->s.number != other->s.number && !G_IsClientDead ( other->client ) && !G_IsClientSpectating( other->client) && (ghost || spec)) + { + return; + } + } + } + + type = ""; + if ( ghost ) + { + type = "*ghost* "; + } + else if ( spec ) + { + type = "*spec* "; + } + + trap_SendServerCommand( other-g_entities, va("%s %d \"%s%s%s\"", + mode == SAY_TEAM ? "tchat" : "chat", + ent->s.number, + type, name, message)); +} + +/* +================== +G_GetChatPrefix +================== +*/ +void G_GetChatPrefix ( gentity_t* ent, gentity_t* target, int mode, char* name, int nameSize ) +{ + const char* namecolor; + char location[64]; + qboolean locationOk = qtrue; + + // Spectators and ghosts dont show locations + if ( ent->client->ps.pm_type == PM_DEAD || G_IsClientSpectating ( ent->client ) ) + { + locationOk = qfalse; + } + + if ( !level.gametypeData->teams && mode == SAY_TEAM ) + { + mode = SAY_ALL; + } + + if ( level.gametypeData->teams ) + { + switch ( ent->client->sess.team ) + { + case TEAM_BLUE: + namecolor = S_COLOR_BLUE; + break; + + case TEAM_RED: + namecolor = S_COLOR_RED; + break; + + default: + namecolor = S_COLOR_WHITE; + break; + } + } + else + { + namecolor = S_COLOR_WHITE; + } + + switch ( mode ) + { + default: + case SAY_ALL: + + Com_sprintf (name, nameSize, "%s%s%s: ", namecolor, ent->client->pers.netname, S_COLOR_WHITE ); + + break; + + case SAY_TEAM: + + if ( locationOk && Team_GetLocationMsg(ent, location, sizeof(location))) + { + Com_sprintf ( name, nameSize, "%s(%s%s) %s(%s): ", + namecolor, + ent->client->pers.netname, + namecolor, + S_COLOR_WHITE, location ); + } + else + { + Com_sprintf ( name, nameSize, "%s(%s%s)%s: ", + namecolor, + ent->client->pers.netname, + namecolor, + S_COLOR_WHITE ); + } + break; + + case SAY_TELL: + + if ( locationOk && target && level.gametypeData->teams && + target->client->sess.team == ent->client->sess.team && + Team_GetLocationMsg(ent, location, sizeof(location)) ) + { + Com_sprintf ( name, nameSize, "%s[%s%s] %s(%s): ", + namecolor, + ent->client->pers.netname, + namecolor, + S_COLOR_WHITE, location ); + } + else + { + Com_sprintf ( name, nameSize, "%s[%s%s]%s: ", + namecolor, + ent->client->pers.netname, + namecolor, + S_COLOR_WHITE ); + } + break; + } + + strcat ( name, S_COLOR_GREEN ); +} + +/* +================== +G_Say +================== +*/ +void G_Say ( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) +{ + int j; + gentity_t *other; + char text[MAX_SAY_TEXT]; + char name[64]; + + // Logging stuff + switch ( mode ) + { + case SAY_ALL: + G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + break; + + case SAY_TEAM: + G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); + break; + } + + // Generate the chat prefix + G_GetChatPrefix ( ent, target, mode, name, sizeof(name) ); + + // Save off the chat text + Q_strncpyz( text, chatText, sizeof(text) ); + + if ( target ) + { + G_SayTo( ent, target, mode, name, text ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) + { + Com_Printf( "%s%s\n", name, text); + } + + // send it to all the apropriate clients + for (j = 0; j < level.numConnectedClients; j++) + { + other = &g_entities[level.sortedClients[j]]; + G_SayTo( ent, other, mode, name, text ); + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { + char *p; + + if ( trap_Argc () < 2 && !arg0 ) { + return; + } + + if (arg0) + { + p = ConcatArgs( 0 ); + } + else + { + p = ConcatArgs( 1 ); + } + + G_Say( ent, NULL, mode, p ); +} + +/* +================== +Cmd_Tell_f +================== +*/ +static void Cmd_Tell_f( gentity_t *ent ) { + int targetNum; + gentity_t *target; + char *p; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc () < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + p = ConcatArgs( 2 ); + + G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); + G_Say( ent, target, SAY_TELL, p ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { + G_Say( ent, ent, SAY_TELL, p ); + } +} + + +static void G_VoiceTo ( gentity_t *ent, gentity_t *other, int mode, const char* name, const char *id, qboolean voiceonly ) +{ + // Only team say is supported right now for voice chatting + if (mode != SAY_TEAM) + { + return; + } + + if (!other || !other->inuse || !other->client) + { + return; + } + + if ( !OnSameTeam(ent, other) ) + { + return; + } + + trap_SendServerCommand( other-g_entities, va("%s %d %d \"%s\" \"%s\"", "vtchat", voiceonly, ent->s.number, name, id)); +} + +/* +================== +G_CanVoiceGlobal + +Can we globaly speak right now +================== +*/ +qboolean G_CanVoiceGlobal ( void ) +{ + if ( level.gametypeData->teams && level.time - level.globalVoiceTime > 5000 ) + { + return qtrue; + } + + return qfalse; +} + +/* +================== +G_VoiceGlobal + +says something out loud that everyone in the radius can hear +================== +*/ +void G_VoiceGlobal ( gentity_t* ent, const char* id, qboolean force ) +{ + if ( !ent ) + { + return; + } + + if ( !level.gametypeData->teams ) + { + return; + } + + if ( !force && level.time - level.globalVoiceTime < 5000 ) + { + return; + } + + level.globalVoiceTime = level.time; + + trap_SendServerCommand( -1, va("vglobal %d \"%s\"", ent->s.number, id)); +} + +/* +================== +G_Voice +================== +*/ +void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) +{ + int j; + gentity_t *other; + char name[MAX_SAY_TEXT]; + + // Spectators and ghosts dont talk + if ( ent->client->ps.pm_type == PM_DEAD || G_IsClientSpectating ( ent->client ) ) + { + return; + } + + // Voice flooding protection on? + if ( g_voiceFloodCount.integer ) + { + // If this client has been penalized for voice chatting to much then dont allow the voice chat + if ( ent->client->voiceFloodPenalty ) + { + if ( ent->client->voiceFloodPenalty > level.time ) + { + return; + } + + // No longer penalized + ent->client->voiceFloodPenalty = 0; + } + + // See if this client flooded with voice chats + ent->client->voiceFloodCount++; + if ( ent->client->voiceFloodCount >= g_voiceFloodCount.integer ) + { + ent->client->voiceFloodCount = 0; + ent->client->voiceFloodTimer = 0; + ent->client->voiceFloodPenalty = level.time + g_voiceFloodPenalty.integer * 1000; + + trap_SendServerCommand( ent-g_entities, va("print \"Voice chat flooded, you will be able use voice chats again in (%d) seconds\n\"", g_voiceFloodPenalty.integer ) ); + + return; + } + } + + G_GetChatPrefix ( ent, target, mode, name, sizeof(name) ); + + if ( target ) + { + G_VoiceTo( ent, target, mode, name, id, voiceonly ); + return; + } + + // send it to all the apropriate clients + for (j = 0; j < level.maxclients; j++) + { + other = &g_entities[j]; + G_VoiceTo( ent, other, mode, name, id, voiceonly ); + } +} + +/* +================== +Cmd_Voice_f +================== +*/ +static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) +{ + char *p; + + if ( trap_Argc () < 2 && !arg0 ) { + return; + } + + if (arg0) + { + p = ConcatArgs( 0 ); + } + else + { + p = ConcatArgs( 1 ); + } + + G_Voice( ent, NULL, mode, p, voiceonly ); +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) +{ + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); +} + +/* +============ +G_VoteDisabled + +determins if the given vote is disabled +============ +*/ +int G_VoteDisabled ( const char* callvote ) +{ + return trap_Cvar_VariableIntegerValue( va("novote_%s", callvote) ); +} + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) +{ + int i; + char arg1[MAX_STRING_TOKENS]; + char arg2[MAX_STRING_TOKENS]; + + if ( !g_allowVote.integer ) + { + trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); + return; + } + + if ( level.intermissiontime || level.intermissionQueued ) + { + trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed during intermission.\n\"" ); + return; + } + + // No voting within the minute of a map change + if ( level.time - level.startTime < 1000 * 60 ) + { + trap_SendServerCommand( ent-g_entities, "print \"Cannot vote within the first minute of a map change.\n\"" ); + return; + } + + if ( level.numConnectedClients > 1 && level.numVotingClients == 1 ) + { + trap_SendServerCommand( ent-g_entities, "print \"You need at least 2 clients to call a vote.\n\"" ); + return; + } + + if ( level.voteTime ) + { + trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); + return; + } + + if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) + { + trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); + return; + } + + if ( ent->client->sess.team == TEAM_SPECTATOR ) + { + trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); + return; + } + + if ( ent->client->voteDelayTime > level.time ) + { + trap_SendServerCommand( ent-g_entities, va("print \"You are not allowed to vote within %d minute of a failed vote.\n\"", g_failedVoteDelay.integer ) ); + return; + } + + // Save the voting client id + level.voteClient = ent->s.number; + + // make sure it is a valid command to vote on + trap_Argv( 1, arg1, sizeof( arg1 ) ); + trap_Argv( 2, arg2, sizeof( arg2 ) ); + + if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + return; + } + + if ( !Q_stricmp( arg1, "map_restart" ) ) { + } else if ( !Q_stricmp( arg1, "mapcycle" ) ) { + } else if ( !Q_stricmp( arg1, "map" ) ) { + } else if ( !Q_stricmp( arg1, "rmgmap" ) ) { + } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { + } else if ( !Q_stricmp( arg1, "kick" ) ) { + } else if ( !Q_stricmp( arg1, "clientkick" ) ) { + } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { + } else if ( !Q_stricmp( arg1, "timelimit" ) ) { + } else if ( !Q_stricmp( arg1, "timeextension" ) ) { + } else if ( !Q_stricmp( arg1, "scorelimit" ) ) { + } else + { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit